我们都知道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) {}
}
}
}
结果可能因快速,慢速单线程和多线程处理器而异。 在我测试的计算机上(没有doSomething),输出如下所示:
{ 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 的前几次迭代工作正常,然后Java'优化': testValue 和 currentValue 始终相等并保持不变虽然线程永远不会离开最里面的循环,但是更改它们的值。我能想到的唯一原因是Java执行乱序执行,如下所示:
do {
testValue = otherThread.getValue();
currentValue = testValue; // moved up from beneath the loop
}
while (testValue == currentValue);
我理解Java编译器中允许乱序执行,因为它可以提高性能,但这些语句显然是相互依赖的。
我的问题很简单:为什么? 为什么 Java以这种方式运行程序?
注意:如果程序是使用参数doSomething启动的,或者如果AdditionThread.currentValue是 volatile ,则代码运行正常。
答案 0 :(得分:4)
您已回答了自己的问题:
如果AdditionThread.currentValue变为volatile,则代码只运行 细
java内存模型不能保证当您从ComparingThread中读取AdditionThread.currentValue时,您将看到AdditionThread中存在的最新版本。如果数据对其他线程可见,则必须使用提供的工具之一,volatile,synchronized,java.util.concurrent。*,以告诉系统您需要可见性保证。
乱序执行不是导致意外行为的优化,只是ComparingThread在自己的堆栈上保留了AdditionThread.currentValue的副本。
打开“doSomething”也会修复它,因为将线程置于休眠状态通常会导致它们在重新唤醒时刷新堆栈,尽管这没有得到正式保证。