总览
许多多线程代码开发人员都熟悉这样的想法,即不同的线程可以对持有的值有不同的看法,这不是唯一的原因,即如果线程不安全,它可能不会看到更改。 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