没有 volatile 变量的同步中断
Broken synchronization without volatile variable
我想了解如果程序员忘记将 volatile
关键字添加到用于同步的变量,从缓存的角度(MESI 协议)会发生什么。
以下代码片段在一些迭代后就停止了。我知道这是因为 2 个线程之一(假设线程 0)没有看到线程 1 由 getNext() 完成的对 current
变量所做的更新,因此它一直循环下去。
但是,我不明白为什么会这样。 THREAD 0 在 current
上循环并且应该在某个时候看到缓存行已被线程 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;
}
}
不同的处理器架构可以有不同程度的高速缓存一致性。有些可能非常小,因为目标是最大化吞吐量,而不必要地将缓存与内存同步会拖慢吞吐量。 Java 在检测到需要协调时会强加自己的内存屏障,但这取决于程序员是否遵守规则。
如果程序员不添加变量可以跨线程共享的指示(如使用同步或易失性关键字),则允许 JIT 假设没有其他线程正在修改该变量,并且它可以优化因此。测试该变量的字节码可能会被彻底重新排序。如果 JIT 对代码重新排序足够多,那么硬件何时检测到修改并获取新值可能无关紧要。
引自 Java 并发实践 3.1:
In the absence of synchronization, the compiler, processor, and runtime can do some downright weird things to the order in which operations appear to execute. Attempts to reason about the order in which memory actions "must" happen in insufficiently synchronized multithreaded programs will almost certainly be incorrect.
(JCIP 书中有几个地方表明对同步不足的代码进行推理是徒劳的。)
我想了解如果程序员忘记将 volatile
关键字添加到用于同步的变量,从缓存的角度(MESI 协议)会发生什么。
以下代码片段在一些迭代后就停止了。我知道这是因为 2 个线程之一(假设线程 0)没有看到线程 1 由 getNext() 完成的对 current
变量所做的更新,因此它一直循环下去。
但是,我不明白为什么会这样。 THREAD 0 在 current
上循环并且应该在某个时候看到缓存行已被线程 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;
}
}
不同的处理器架构可以有不同程度的高速缓存一致性。有些可能非常小,因为目标是最大化吞吐量,而不必要地将缓存与内存同步会拖慢吞吐量。 Java 在检测到需要协调时会强加自己的内存屏障,但这取决于程序员是否遵守规则。
如果程序员不添加变量可以跨线程共享的指示(如使用同步或易失性关键字),则允许 JIT 假设没有其他线程正在修改该变量,并且它可以优化因此。测试该变量的字节码可能会被彻底重新排序。如果 JIT 对代码重新排序足够多,那么硬件何时检测到修改并获取新值可能无关紧要。
引自 Java 并发实践 3.1:
In the absence of synchronization, the compiler, processor, and runtime can do some downright weird things to the order in which operations appear to execute. Attempts to reason about the order in which memory actions "must" happen in insufficiently synchronized multithreaded programs will almost certainly be incorrect.
(JCIP 书中有几个地方表明对同步不足的代码进行推理是徒劳的。)