3.4 volatile的内存语义
3.4.1 volatile的特性
理解volatile特性的一个好方法是对volatile变量的单个读、写,看成是使用同一个锁对这些单个读、写操作做了同步。
class VolatileFeaturesExample{
volatile long v1 = 0L; //使用volatile声明64位的long型变量
public void set(long l){
v1 = l; //单个volatile变量的写
}
public void getAndIncrement(){
v1++; //复合(多个)volatile变量的读、写
}
public long get(){
return v1; //单个volatile变量的读
}
}
假设有多个线程分别调用上面程序的3个方法,这个程序在语义上和下面的程序等价。
class VolatileFeaturesExample(){
long v1 = 0L;
public synchronized void set(long l){ //对单个的普通变量的写用一个锁同步
v1 = l;
}
public void getAndIncrement(){ //普通方法调用
long temp = get(); // 调用已经同步的读方法
temp += 1L; // 普通的写操作
set(temp); // 调用已经同步的写方法
}
public synchronzied long get(){
return v1;
}
}
简而言之,volatile变量自身具有如下特性:
- 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量的最后的写入
- 原子性:对任意**单个**volatile变量的读、写具有原子性
3.4.2 volatile写-读建立的happens-before关系
从JSR-133(即JDK5开始),volatile变量的读、写可以实现线程之间的通信。
如下代码:
class VolatileExample(){
int a = 0;
volatile boolean flag = false;
public void writer(){
a = 1; // 1
flag = true; // 2
}
public void reader(){
if(flag){ // 3
int i = a ; // 4
.....
}
}
}
假设线程A执行writer()方法之后,线程B执行reader()方法,其happens-before关系的图形化表现形式如下:
3.4.3 volatile写-读的内存语义
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来从主内存中读取共享变量。
3.4.4 volatile内存语义的实现
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
3.4.5 JSR-133为什么要增强volatile的内存语义
为了提供一种比锁更轻量级的线程之间通信的机制
在功能上,锁比volatile更强大;在可伸缩性和执行性能上,volatile更有优势。