什么是“可见性”
-
可见性
可见性是指一个线程修改一个共享变量时,另一个线程能读取到这个修改的值。
关于volatile
-
Volatile
写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
volatile会插入一个内存屏障,对volatile变量的读或写操作,之后的代码不会重排序到该操作之前。
volatile是轻量级synchronized,它保证了共享变量的“可见性”。它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。
如果一个字段被声明为volatile,JVM会确保所有线程按到这个变量的值是一致的。意思就是线程A修改了一个共享变量a的值从1变为2,对线程A而言,a的值就是2。
JVM会确保其他线程读取a的值跟线程A一样都为2,这是一个“实时的”,即这时线程B读取a的值一定是2。
CPU术语定义
volatile生成的汇编指令:
Java代码如下。
instance = new Singleton(); // instance是volatile变量
转变成汇编代码,如下。
0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);
lock前缀的指令在多核处理器下会引发两件事情:
1)将当前处理器缓存行数据写到系统内存
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。
但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。
所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改。
就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
图解
目前主流的英特尔酷睿处理器的L1、L2或L3缓存的高速缓存行是64个字节宽。不支持部分填充缓存行。
Java中,byte 是字节数据类型 ,是有符号型的,占1 个字节;大小范围为-128—127 。
char 是字符数据类型 ,是无符号型的,占2字节(Unicode码);大小范围 是0—65535。 String是一个char数组。
如果队列的头节点和尾节点都不足64字节的话,处理器会将它们都读到同一个高速缓存行中,在多处理器下每个处理器都会缓存同样的头、尾节点。
当一个处理器试图修改头节点时,会将整个缓存行锁定,那么在缓存一致性机制的作用下,会导致其他处理器不能访问自己高速缓存中的尾节点,
而队列的入队和出队操作则需要不停修改头节点和尾节点,所以在多处理器的情况下将会严重影响到队列的入队和出队效率。
不过这种追加字节的方式在Java 7下可能不生效,因为Java 7变得更加智慧,它会淘汰或重新排列无用字段,需要使用其他追加字节的方式。
参考:
《Java并发编程艺术》