理解volatile特性的一个好方法是把volatile变量的单个读/写.看成是使用同一个锁对这些单个读/写操作做出了同步。其实,volatile的读写操作与锁的获取与释放有这对应的关系,我们来慢慢细看,首先看一下volatile的特性:
volatile的特性
class VolatileFeaturesExample{
volatile long v1 = 0l;
public void set(long l){
v1 = l; // 单个volatile变量的写
}
public void getAndIncrement(){
v1 ++; // 复合 volatile变量的读/写
}
public long get(){
return v1; // 单个volatile变量的读
}
}
假设有多个线程分别调用上面的3个方法,这个程序在语义上与下面的程序等价:
class VolatileFeaturesExample{
volatile long v1 = 0l;
public synchronized void set(long l){
v1 = l;
}
public void getAndIncrement(){ // 普通方法调用
long temp = get(); // 调用已同步的读方法
temp += 1l; // 普通写操作
set(temp); //调用已同步的写方法
}
public synchronized long get(){ // 对单个的普通变量的读写用同一个锁同步
return v1;
}
}
如上面的实例程序所示,一个volatile变量的读写操作,与一个普通变量的读写操作都是使用同一个锁来同步,他们之间的执行效果相同。
锁的happens-before规则保证了释放锁和获取锁的两个线程之间的内存可见性,这意味着对一个volatile变量的读,总能看到(任意线程)对这个volatile变量最后的写。锁的语义决定了临界区执行的原子性,这意味着64位Long类型和double类型变量,只要他是volatile变量,对该变量的读写具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作,这种操作整体上不具有原子性。简言之,volatile变量自身具有以下特性:
- 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
- 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
volatile写-读建立的happens-before关系
在JDK 5开始,volatile变量的写-读可以实现线程的通信。从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果:volatile的写与锁的释放具有相同的内存语义,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规则,建立的happens-before关系如下:
- 根据程序次序规则: 1 happens-before 2 ,3 happens-before 4;
- 根据volatile规则: 2 happens-before 3;
- 根据happens-before规则的传递性: 1 happens-before 4 ;
黑色箭头表示程序顺序规则;红色箭头表示volatile规则;蓝色箭头表示组合这些规则后提供的happens-before保证;
volatile 写-读的内存语义
写语义为:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。就如上面的代码来说:线程A在写flag变量后,本地内存A中被线程A更新过的两个共享变量的值会被刷新到主内存中,此时,本地内存A和主内存中的共享变量的值是一致的。读语义是:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
下面我们对volatile写和volatile读的内存语义做一下总结:
- 线程A 写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做的修改)消息
- 线程B读一个volatile变量,实质上是线程B接受了之前某个线程发出的(在写这个volatile变量之前对共享变量所做的修改的消息)
- 线程A写一个volatile变量,随后线程B读这个volatile,这个过程实质上是线程A通过主内存向线程B发送消息
当然了,这里的发送消息只是逻辑上的,便于理解;