總覽
許多多線程代碼開發人員都熟悉這樣的想法,即不同的線程可以對持有的值有不同的看法,這不是唯一的原因,即如果線程不安全,它可能不會看到更改。 JIT本身可以發揮作用。
為什么不同的線程看到不同的值?
當您有多個線程時,它們將嘗試例如通過嘗試訪問同一內存來最小化它們將交互的數量。 為此,他們有一個單獨的
本地副本,例如在1級緩存中。 該緩存通常最終是一致的。 我看到兩個線程看到不同值的短時間介于一微秒到十毫秒之間。 最終,線程被上下文切換,緩存被清除或更新。 無法保證何時會發生這種情況,但幾乎總是不到一秒鐘。
JIT如何發揮作用?
Java內存模型說,不能保證不是線程安全的字段將看到更新。 這允許JIT進行優化,將僅讀取而不寫入的值有效地內聯到代碼中。 這意味著即使更新了緩存,更改也可能不會反映在代碼中。
一個例子
該代碼將一直運行,直到將布爾值設置為false為止。
>static class MyTask implements Runnable {private final int loopTimes;private boolean running = true;boolean stopped = false;public MyTask(int loopTimes) {this.loopTimes = loopTimes;}@Overridepublic void run() {try {while (running) {longCalculation();}} finally {stopped = true;}}private void longCalculation() {for (int i = 1; i < loopTimes; i++)if (Math.log10(i) < 0)throw new AssertionError();}
}public static void main(String... args) throws InterruptedException {int loopTimes = Integer.parseInt(args[0]);MyTask task = new MyTask(loopTimes);Thread thread = new Thread(task);thread.setDaemon(true);thread.start();TimeUnit.MILLISECONDS.sleep(100);task.running = false;for (int i = 0; i < 200; i++) {TimeUnit.MILLISECONDS.sleep(500);System.out.println("stopped = " + task.stopped);if (task.stopped)break;}
}
此代碼反復執行一些對內存沒有影響的工作。 它唯一的區別是需要多長時間。 通過花費更長的時間,它將確定在運行之前或之后將run()中的代碼優化為false。
如果我使用10或100和-XX:+ PrintCompilation運行此命令,則會看到
111 1 java.lang.String::hashCode (55 bytes)
112 2 java.lang.String::charAt (29 bytes)
135 3 vanilla.java.perfeg.threads.OptimisationMain$MyTask :longCalculation (35 bytes)
204 1 % ! vanilla.java.perfeg.threads.OptimisationMain$MyTask :run @ 0 (31 bytes)
stopped = false
stopped = false
stopped = false
stopped = false
... many deleted ...
stopped = false
stopped = false
stopped = false
stopped = false
stopped = false
如果我用1000運行它,您會看到run()尚未編譯并且線程停止
112 1 java.lang.String::hashCode (55 bytes)
112 2 java.lang.String::charAt (29 bytes)
133 3 vanilla.java.perfeg.threads.OptimisationMain $MyTask::longCalculation (35 bytes)
135 1 % vanilla.java.perfeg.threads.OptimisationMain $MyTask::longCalculation @ 2 (35 bytes)
stopped = true
一旦線程被編譯,即使線程將進行多次上下文切換等,更改也永遠不會被看到。
如何解決這個問題
簡單的解決方案是使該字段易變。 這將確保該字段的值是一致的,而不僅僅是最終一致的,這就是緩存可能為您執行的操作。
結論
雖然有許多類似的問題示例; 為什么我的線程沒有停止? 答案更多與Java內存模型有關,Java內存模型允許JIT“內聯”它執行硬件的字段,并在不同的緩存中具有多個數據副本。
參考: Vanilla Java博客上的JCG合作伙伴 Peter Lawrey提供的Java內存模型和優化 。
翻譯自: https://www.javacodegeeks.com/2013/01/java-memory-model-and-optimisation-2.html