volatile关键字的理解
当我们声明一个变量为 volatile 时,程序对于该变量的读和写就会变得十分特别,具体的特别之处,我们还是要看看 volatile 关键字的内存语义,观察其特别之处。
下面是一个简单的例子,通过这个例子我们来具体说明 volatile 关键字的内存语义
class VolatileFeatures {
volatile long value = 0L;
pulbic void set(long val) {
value = val;
}
public void getIncreament() {
value++;
}
public long get() {
return value;
}
这看似是一个简单的例子,在一个类里面定义变量,给出对应的@Setter 和 @Getter 方法。但是,我们定义的变量使用了 volatile 关键字进行修饰。这也正是特殊之处,当我们在程序中进行调用此处的 value 变量时,对其进行读写操作,相当于对一个普通变量使用同一个锁来同步,二者的执行效果是相同的。所以,上面的案例可以等同于下面的例子
class TheSameAsVolatileFeatures {
//定义一个普通变量
long value = 0L;
//对普通变量的写操作使用同一个锁进行同步
public synchronized void set(long val) {
value = val;
}
//对于普通变量进行普通操作
public void getIncreament() {
//调用同步读方法
long v = get();
//进行普通操作
v += 1L;
//调用同步写方法
set(v);
}
//对普通变量的读操作使用同一个锁进行同步
public synchronized long get() {
return value;
}
通过上面的例子可以看出,由于锁的happens-before原则保证了释放锁与获取锁的两个线程内存可见,这也就是我们 volatile 关键字的特殊之处
对于一个 volatile 变量的读,总能看到任意线程对于该变量最后的写入
至此可以总结出, volatile 变量有一下特性
可见性:对于一个 volatile 变量的读,总能看到任意线程对于该 volatile 变量的最后写入。
原子性:对任意单个 volatile 变量的读与写均具有原子性,但是,类似于 volatile++ 这种复合操作,将不再具有原子性。
volatile 关键字的内存语义
讲到内存语义这块,volatile 变量的读 - 写 与锁的释放 - 获取具有相同的内存效果:
volatile写和锁的释放具有相同的内存语义
volatile读和锁的获取具有相同的内存语义
综合来说的话,volatile 关键字的读写内存语义总结如下:
1、线程A写一个 volatile 变量,实质上是线程A向接下来将要读该 volatile 变量的线程发出了(对于共享变量进行修改)消息。
2、线程B要读一个 volatile 变量,实质上是线程B收到了之前某个线程修改共享变量的消息,这样保证了该变量的一致性和同步性。
3、线程A写 volatile 变量,线程B读 volatile 变量,实质上二者的信息交流是通过主内存(共享内存)来进行的。