如果程序员忘记将volatile
关键字添加到用于同步的变量中,我想了解缓存的观点(MESI协议)会发生什么。
以下代码片段在一些迭代后停止。我知道它是因为2个线程中的一个(让我们假设THREAD为0)没有看到由getNext()完成THREAD 1对current
变量的更新,因此它会永远循环
但是,我不明白为什么会这样。 THREAD 0在current
上循环,并且应该在某个时刻看到高速缓存行由THREAD 1更新(高速缓存行切换到修改状态)并发出" Read"内存总线上的消息,用于在本地缓存中获取它。将volatile
修饰符添加到current
变量将使一切正常。
正在发生什么阻止THREAD 0继续执行?
参考:Memory Barriers: a Hardware View for Software Hackers
public class Volatile {
public static final int THREAD_0 = 0;
public static final int THREAD_1 = 1;
public static int current;
public static int counter = 0;
public static void main(String[] args) {
current = 0;
/** Thread 0 **/
Thread thread0 = new Thread(() -> {
while(true) { /** THREAD_0 */
while (current != THREAD_0);
counter++;
System.out.println("Thread0:" + counter);
current = getNext(THREAD_0);
}
});
/** Thread 1 **/
Thread thread1 = new Thread(() -> {
while(true) { /** THREAD_1 */
while (current != THREAD_1);
counter++;
System.out.println("Thread1:" + counter);
current = getNext(THREAD_1);
}
});
thread0.start();
thread1.start();
}
public static int getNext(int threadId) {
return threadId == THREAD_0 ? THREAD_1 : THREAD_0;
}
}
答案 0 :(得分:0)
不同的处理器架构可以具有不同程度的缓存一致性。有些可能是非常小的,因为目标是最大化吞吐量并且不必要地将具有内存的高速缓存同步将其拖下来。当Java检测到需要协调时,它会强加自己的内存障碍,但这取决于遵循规则的程序员。
如果程序员没有添加指示(比如使用synchronized或volatile关键字)可以跨线程共享变量,则允许JIT假设没有其他线程正在修改该变量,并且它可以优化因此。测试该变量的字节码可以大大重新排序。如果JIT重新排序代码足够,那么当硬件检测到修改并获取新值时可能无关紧要。
在练习3.1中引用Java并发:
在没有同步的情况下,编译器,处理器和运行时可以对操作似乎执行的顺序做一些彻头彻尾的奇怪事情。尝试推断记忆动作的顺序"必须"发生在不充分同步的多线程程序中几乎肯定是不正确的。
(在JCIP的书中有几个地方提到了关于代码不充分同步的推理是徒劳的。)