2013-05-07 4 views
1

Java가 코드를 매우 철저하게 최적화한다는 것을 알고 있습니다. 글쎄, 대부분의 시간.동일한 스레드에서 순서가 서로 다르게 코드가 실행됩니다.

public class BrokenOptimizationTest { 

/** 
* This thread constantly polls another thread object's private field. 
*/ 
public static class ComparingThread extends Thread { 
    private int currentValue = 0; 
    private AdditionThread otherThread = null; 

    public ComparingThread(AdditionThread add) { 
     this.otherThread = add; 
    } 

    @Override 
    public void run() { 
     while (true) { 
      int testValue = currentValue; 

      if (BrokenOptimizationTest.shouldDoSomething) { 

       do { 
        testValue = otherThread.getValue(); 
        BrokenOptimizationTest.doSomething(); 
        // System.out.println(testValue); // to see testValue really changes 
       } 
       while (testValue == currentValue); 

      } 
      else { 

       do { 
        testValue = otherThread.getValue(); 
        // System.out.println(testValue); // to see testValue really changes 
       } 
       while (testValue == currentValue); 

      } 

      System.out.println("{ testValue: " + testValue + ", currentValue: " + currentValue + " }"); 

      currentValue = testValue; 
     } 
    } 
} 

/** 
* This thread often adds to its pollable value. 
*/ 
public static class AdditionThread extends Thread { 
    private int currentValue = 0; 
    public long queryCount = 0; 

    public int getValue() { 
     ++queryCount; 
     return currentValue; 
    } 

    @Override 
    public void run() { 
     while (true) { 
      ++currentValue; 

      //I said 'often', so sleep some more 
      try { 
       Thread.sleep(1); 
      } 
      catch (InterruptedException e) {} 
     } 
    } 
} 

/** 
* Whether or not the program must simulate doing an expensive calculation between consecutive queries. 
*/ 
public static boolean shouldDoSomething = false; 

/** 
* Simulates doing an expensive calculation 
*/ 
public static void doSomething() { 
    try { 
     Thread.sleep(0, 100); 
    } 
    catch (InterruptedException e) {} 
} 


/** 
* Call the program with something like "slow" to enable doSomething 
*/ 
public static void main(String[] args) { 
    if (args.length >= 1 && (args[0].toLowerCase().contains("slow") || args[0].toLowerCase().contains("dosomething"))) 
     shouldDoSomething = true; 


    AdditionThread addThread = new AdditionThread(); 
    ComparingThread compThread = new ComparingThread(addThread); 
    addThread.start(); 
    compThread.start(); 

    /** 
    * Print the current program state every now and then. 
    */ 
    while (true) { 
     System.out.println("{ currentValue: " + addThread.getValue() + ", activeThreads: " + Thread.activeCount() + ", queryCount: " + addThread.queryCount + " }"); 
     System.out.flush(); 

     try { 
      Thread.sleep(1000); 
     } 
     catch (InterruptedException e) {} 
    } 
} 
} 

결과는 빠르고 느린 단일 스레드 및 멀티 스레드 프로세서에 따라 다를 수 있습니다 다음은 정말 내 머리 놨 코드의 조각이다.

{ currentValue: 1, activeThreads: 3, queryCount: 1 } 
{ testValue: 1, currentValue: 0 } 
{ testValue: 2, currentValue: 1 } 
{ testValue: 3, currentValue: 2 } 
{ testValue: 4, currentValue: 3 } 
{ testValue: 5, currentValue: 4 } 
{ testValue: 6, currentValue: 5 } 
{ testValue: 7, currentValue: 6 } 
{ testValue: 8, currentValue: 7 } 
{ testValue: 9, currentValue: 8 } 
{ testValue: 10, currentValue: 9 } 
{ testValue: 11, currentValue: 10 } 
{ testValue: 12, currentValue: 11 } 
{ testValue: 13, currentValue: 12 } 
{ currentValue: 994, activeThreads: 3, queryCount: 2176924819 } 
{ currentValue: 1987, activeThreads: 3, queryCount: 4333727079 } 
{ currentValue: 2980, activeThreads: 3, queryCount: 6530688815 } 
{ currentValue: 3971, activeThreads: 3, queryCount: 8723797559 } 

처음 몇 CompareThread 워크 아웃 벌금 반복하고 자바 '를 최적화': 나는 (해봐요없이) 테스트 컴퓨터에서 는, 출력은 다음과 같이 보이는 testValuecurrentValue은 항상 동일하며 스레드가 가장 안쪽 루프를 벗어나지 않지만 계속해서 값을 변경합니다.

do { 
    testValue = otherThread.getValue(); 
    currentValue = testValue; // moved up from beneath the loop 
} 
while (testValue == currentValue); 

가 나는 그것이 성능을 향상시킬 수 있기 때문에 실행이 자바 컴파일러에서 허용되는 아웃 오브 오더 이해하지만이 : 내가 생각할 수있는 유일한 원인은, 자바, 순서가 실행을 수행과 같이이다 진술은 분명히 서로 의존적이다.

제 질문은 간단합니다 : ? Java가이 방법으로 프로그램을 실행합니까?

참고 : doSomething 매개 변수를 사용하여 프로그램을 시작한 경우 또는 AdditionThread.currentValue를 휘도가으로 설정하면 코드가 올바르게 실행됩니다.

답변

4

당신은 당신의 자신의 질문에 대답했습니다 AdditionThread.currentValue 휘발성 이루어진 경우

코드는 벌금을 실행합니다.

Java 메모리 모델은 AdditionThread에있는 최신 버전을 볼 수있는 ComparingThread 내부에서 AdditionThread.currentValue를 읽을 때 어떤 보장도하지 않습니다. 데이터가 다른 스레드에 표시되도록하려면 제공되는 도구 중 하나 인 휘발성, 동기화 된 java.util.concurrent. * 중 하나를 사용해야 시스템에 가시성 보장이 필요하다는 것을 알릴 수 있습니다.

예상치 못한 동작을 유발하는 최적화가 아닌 실행은 AdditionThread.currentValue의 복사본을 자체 스택에 보관하는 ComparingThread 일뿐입니다.

"doSomething"을 켜면 스레드를 절전 모드로 전환하면 일반적으로 다시 작동 할 때 스택을 새로 고칠 수 있으므로 공식적으로 보장되지는 않지만이를 수정합니다.

+0

아, 거의 설명 할 수 있습니다. 나를 괴롭히는 유일한 문제는 println의 주석 처리가 제거되었을 때 콘솔에서 testValue가 증가하는 이유는 무엇입니까? – Flumble

+0

두 번째로 생각해 보면, testValue에 대해 위에 게시 한 내용을 재현 할 수 없어서 제 상상력이 될 수도 있습니다. 신속하고 완전한 답변 주셔서 감사합니다. :) – Flumble

+0

println은 시스템 리소스가 사용 가능할 때까지 대기 상태에서 다시 돌아올 때 스레드가 해당 상태를 다시 설정하게하는 것과 비슷한 효과를 가질 수 있습니다. 따라서 동기화가 이루어지지 않아 발생하는 문제를 "숨길"수 있습니다. 여기를 참조하십시오 : http://stackoverflow.com/a/11570673/331052 – Affe

관련 문제