volatile是一个Java语言的关键字,在线程同步上有一席之地。相较于其他线程同步方法,volatile的使用简单,性能较好,同时也有其局限性。
内存模型
- 每个线程在处理器的高速缓存中拥有一份工作内存,保存它用到的共享变量的副本。
- 工作内存中的变量从主内存中读取拷贝,修改后写回主内存。
并发条件
并发编程时需满足三个条件才能正常工作。
可见性
- 一个线程修改了变量,其他线程立即要能看到。
- 通常工作内存中的变量修改后,不会立即写回主内存,从而产生脏读。
原子性
- Do it all or don’t do it at all。
- 只有最基本的赋值是原子操作,变量互相赋值、自增减均不是原子操作。
有序性
指令重排序:
- 处理器为提高效率对代码的执行顺序优化,但保证执行结果和顺序执行的结果相同。
1 | int i = 0; |
1 | int a = 10; // 1 |
- 多线程时,当依赖某个变量来判断语句的执行时,可能会因为指令重排序出错。
1 | // T1 |
- JVM有序性原则:happens-before(共8条)
- 单线程内执行结果“看起来”顺序(仍可能重排序)。
- 对同一个锁,unlock先于lock。
- 一个线程写一个变量,然后一个线程读该变量,则写先于读。
- A先于B,B先于C,则A先于C。
Volatile与并发
可见性
volatile可保证可见性:对变量的修改立即写回主内存;读取变量值时会从主内存读取。
原子性
volatile无法保证原子性:当面对非原子操作时,单句代码会被拆解执行。
1 | volatile int inc = 10; |
inc++包含三步:从主内存读inc,计算inc’=inc+1,写主内存inc=inc’,以下情况会出错:
- T1从主内存读inc=10,写入T1工作内存。
- T2从主内存读inc=10,写入T2工作内存。
- T1计算inc’=11并写工作内存及主内存inc=11。
- T2计算inc’=11并写工作内存及主内存inc=11。
有序性
volatile可保证有序性:禁止指令重排序。
原则:
- 执行对volatile的读/写操作前,前面的更改一定全部执行,且结果对后面可见;后面的更改一定没有执行。
- 指令优化时,不能把对volatile的读/写操作放在其后,也不能把volatile读/写后面的操作放在其前。
1 | //x、y为非volatile变量 |
实际例子中保证有序性:
1 | volatile boolean inited; |
使用场景
- 状态标记
利用有序性及可见性,需规避非原子性操作。 - 单例模式double check。
1 | class Singleton { |