内存屏障与JVM并发2011-09-04 infoq 崔康译内存屏障,又称内存栅栏,是一组处理器指令,用于实现对内存操作的顺序限 制。本文介绍了内存屏障对多线程程序的影响。我们将研究内存屏障与JVM并发机 制的关系,如易变量(volatile)、同步(synchronized)和原子条件式 (atomic conditional)。本文假定读者已经充分掌握了相关概念和Java内存模 型,不讨论并发互斥、并行机制和原子性。内存屏障用来实现并发编程中称为可 见性(visibility)的同样重要的作用。内存屏障为何重要?对主存的一次访问一般花费硬件的数百次时钟周期。处理器通过缓存 (caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定 内存操作的顺序。也就是说,程序的读写操作不一定会按照它要求处理器的顺序 执行。当数据是不可变的,同时/或者数据限制在线程范围内,这些优化是无害的 。如果把这些优化与对称多处理(symmetric multi-processing)和共享可变状 态(shared mutable state)结合,那么就是一场噩梦。当基于共享可变状态的 内存操作被重新排序时,程序可能行为不定。一个线程写入的数据可能被其他线 程可见,原因是数据写入的顺序不一致。适当的放置内存屏障通过强制处理器顺 序执行待定的内存操作来避免这个问题。内存屏障的协调作用内存屏障不直接由JVM暴露,相反它们被JVM插入到指令序列中以维持语言层并 发原语的语义。我们研究几个简单Java程序的源代码和汇编指令。首先快速 看一 下Dekker算法中的内存屏障。该算法利用volatile变量协调两个线程之间的共享 资源访问。请不要关注该算法的出色细节。哪些部分是相关的?每个线程通过发信号试图 进入代码第一行的关键区域。如果线程在第三行意识到冲突(两个线程都要访问 ),通 过turn变量的操作来解决。在任何时刻只有一个线程可以访问关键区域。// code run by first thread // code run by second thread
1 intentFirst = true; intentSecond = true;
2
3 while (intentSecond) while (intentFirst) // volatile read
4 if (turn != 0) { if (turn != 1) { // volatile read
5 intentFirst = false; intentSecond = false;
6 while (turn != 0) {} while (turn != 1) {}
7 intentFirst = true; intentSecond = true;
8 } }
9
10 criticalSection(); criticalSection();
11
12 turn = 1; turn = 0; // volatile write
13 intentFirst = false; intentSecond = false; // volatile write