volatile是轻量级的synchronized,他在多线程开发中保证了共享变量的“可见性”。
可见性:当一个线程修改一个共享变量时,另外一个线程能够读到这个修改的值。
实际上我们需要记住volatile的三层语义:
-
保证可见性
-
不保证原子性
-
禁止指令重排
1. volatile的定义与实现原理
定义:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了
volatile
关键字,在某些情况下比锁要更加方便。如果一个字段被声明成volatile
,Java线程内存模型确保所有线程看到这个变量的值是一致的。
那么volatile是如何保证可见性的呢?
有volatile
变量修饰的共享变量进行写操作的时候会多出一行汇编代码
lock addl $0×0,(%esp);
Lock#
前缀的指令在多核处理器下引发了两件事
-
将当前处理器缓存行的数据写回到系统内存。
-
这个写回内存的操作会使在其他
CPU
里缓存了该内存地址的数据无效。处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。
总线(也叫CPU总线)是所有CPU与芯片组连接的主干道,负责CPU与外界所有部件的通信,包括高速缓存、内存、北桥,其控制总线向各个部件发送控制信号、通过地址总线发送地址信号指定其要访问的部件、通过数据总线双向传输。
2. volatile的使用优化
著名的Java并发编程大师Doug lea
在JDK 7
的并发包里新增一个队列集合类LinkedTransferQueue
,它在使用volatile
变量时,用一种追加字节的方式来优化队列出队和入队的性能。
/** 队列中的头部节点 */ private transient final PaddedAtomicReference<QNode> head; /** 队列中的尾部节点 */ private transient final PaddedAtomicReference<QNode> tail; static final class PaddedAtomicReference <T> extends AtomicReference T> { // 使用很多4个字节的对象引用追加到64个字节 Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe; PaddedAtomicReference(T r) { super(r); } } public class AtomicReference <V> implements java.io.Serializable { private volatile V value; // 省略其他代码 }
它使用一个内部类类型来定义队列的 头节点(head)和尾节点(tail),而这个内部类PaddedAtomicReference
相对于父类AtomicReference
只做了一件事情,就是将共享变量追加到64字节。
那么看到这里,小伙伴们大多会有疑惑:为什么追加字节能够提高并发编程的效率?那么就需要先了解一下缓存行的概念。
缓存行:缓存中可以分配最小存储单位。缓存行是2的整数幂个连续自己,一般为32~256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,若这些变量共享一个缓存行的话,就会影响彼此的性能,即伪共享问题。
3. 伪共享问题
举例说明:在核心1上运行的线程想更新变量X
,同时核心2上的线程想要更新变量Y
。不幸的是,这两个变量在同一个缓存行中。每个线程都要去竞争缓存行的所有权来更新变量。如果核心1获得了所有权,缓存子系统将会使核心2中对应的缓存行失效。当核心2获得了所有权然后执行更新操作,核心1就要使自己对应的缓存行失效。这会来来回回的经过L3
缓存,大大影响了性能。如果互相竞争的核心位于不同的插槽,就要额外横跨插槽连接,问题可能更加严重。
为什么追加64字节能够提高并发编程的效率?
因为对于绝大多数处理器的L1、L2或L3缓存的高速缓存行是64个字节宽,且不支持部分填充缓存行。这意味着,如果队列的头节点和尾节点都不足64字节的话,处理器会将它们都读到同一个高速缓存行中,在多处理器下每个处理器都会缓存同样的头、尾节点,当一个处理器试图修改头节点时,会将整个缓存行锁定,那么在缓存一致性机制的作用下,会导致其他处理器不能访问自己高速缓存中的尾节点,而队列的入队和出队操作则需要不停修改头节点和尾节点,所以在多处理器的情况下将会严重影响到队列的入队和出队效率。Doug lea
使用追加到64字节的方式来填满高速缓冲区的缓存行,避免头节点和尾节点加载到同一个缓存行,使头、尾节点在修改时不会互相锁定。
参考文献
-
《Java并发编程的艺术》