volatile在Java并发编程中常用于保持内存可见性和防止指令重排序。内存可见性(Memory Visibility):所有线程都能看到共享内存的最新状态;防止指令重排:在基于偏序关系的Happens-Before内存模型中,指令重排技术大大提高了程序执行效率,但同时也引入了一些问题。
volatile保持内存可见性的特殊规则:read、load、use动作必须连续出现;assign、store、write动作必须连续出现;每次读取前必须先从主内存刷新最新的值;每次写入后必须立即同步回主内存当中。
也就是说,volatile关键字修饰的变量看到的随时是自己的最新值。在线程1中对变量v的最新修改,对线程2是可见的。
volatile防止指令重排的策略:在每个volatile写操作的前面插入一个StoreStore屏障;在每个volatile写操作的后面插入一个StoreLoad屏障;在每个volatile读操作的后面插入一个LoadLoad屏障;在每个volatile读操作的后面插入一个LoadStore屏障。
注意:只有在Happens-Before内存模型中才会出现这样的指令重排序问题。
1.volatile最适用一个线程写,多个线程读的场合。
如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替。(摘自Netty权威指南)
疑问:如果只是赋值的原子操作,是否可以多个线程写?(答案:可以,但是一般没有这样的必要,即没有这样的应用场景)
最经典的使用案例:
volatile boolean shutdownRequested;...public void shutdown() { shutdownRequested = true; }public void doWork() { while (!shutdownRequested) { // do stuff} }
使用场景2:
结合使用 volatile 和 synchronized 实现 “开销较低的读-写锁”
volatile 允许多个线程执行读操作,因此当使用 volatile 保证读代码路径时,要比使用锁执行全部代码路径获得更高的共享度 —— 就像读-写操作一样。
public class CheesyCounter {private volatile int value;public int getValue() { return value; }public synchronized int increment() {return value++;} }
或者
private volatile long start = System.currentTimeMillis();public synchronized long get() {return start++;}
正确使用 volatile 变量的条件
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
- 对变量的写操作不依赖于当前值。
- 该变量没有包含在具有其他变量的不变式中。
更多使用场景可参考:
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html