volatile
volatile的内存语义
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中
- 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量
什么是内存屏障
-
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序
-
内存屏障其实就是一种 JVM 指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性
-
内存屏障之前的所有写操作都要回写到主内存,内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)
-
因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前,一句话:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读,也叫写后读
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad | Load1; LoadLoad; Load2 | 保证load1的读取操作在load2及后续读取操作之前执行 |
StoreStore | Store1; StoreStore; Store2 | 在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存 |
LoadStore | Load1: LoadStore: Store2 | 在stroe2及其后的写操作执行前,保证load1的读操作已读取结束 |
StoreLoad | Store1; StoreLoad; Load2 | 保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行 |
特性
保证可见性
保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可见
Java内存模型中定义的8种工作内存与主内存之间的原子操作:read(读取)→load(加载)→use(使用)→assign(赋值)→store(存储)→write(写入)→lock(锁定)→unlock(解锁),具体如下图
- read:作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
- load:作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
- use:作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
- assign:作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
- store:作用于工作内存,将赋值完毕的工作变量的值写回给主内存
- write:作用于主内存,将store传输过来的变量值赋值给主内存中的变量
- lock:作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程
- unlock:作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
没有原子性
volatile变量的复合操作(如i++)不具有原子性
要use(使用)一个变量的时候必需load(载入),要载入的时候必需从主内存read(读取)这样就解决了读的可见性。
写操作是把assign和store做了关联(在assign(赋值)后必需store(存储)),store(存储)后write(写入),也就是做到了给一个变量赋值的时候一串关联指令直接把变量值写到主内存
就这样通过用的时候直接从主内存取,在赋值到直接写回主内存做到了内存可见性
对于volatile变量,JVM只是保证从主内存加载到线程工作内存的值是最新的,也就是数据加载时是最新的,但无法保证原子性,对于多线程修改共享变量的场景必须使用加锁同步
指令禁重排
- 在每个 volatile 写操作的前面插入⼀个 StoreStore 屏障
- StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中
- 在每个 volatile 写操作的后面插入⼀个 StoreLoad 屏障
- StoreLoad屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序
- 在每个 volatile 读操作的后面插入⼀个 LoadLoad 屏障
- LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序
- 在每个 volatile 读操作的后面插入⼀个 LoadStore 屏障
- LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序
总结
- volatile 写之前的操作,禁止重排序到 volatile 之后
- volatile 读之后的操作,禁止重排序到 volatile 之前
- volatile 写之后 volatile 读,禁止重排序
如何正确使用volatile
- 单一赋值
- 状态标志,判断业务是否结束
- 开销较低的读,写锁策略
- DCL双端锁
- 单例模式
- 静态内部类