并发系列之volatile解析
1、volatile简介
volatile 具有可见性和有序性的特性,同时,对 volatile 修饰的变量进行单个读写操作是具有原子性。
2、可见性
简单的说,就是多线程运行时,一个线程修改 volatile 共享变量后,其他线程获取值时,一定都是这个修改后的值。
public class WtyicyVolatile {
private static boolean flag = true;
public static void reader() {
new Thread(() -> {
while (flag) {
}
}, "A").start();
}
public static void writer() {
new Thread(() -> {
// 避免 B 线程比 A 线程先运行修改 flag 值
try {
TimeUnit.SECONDS.sleep(1);
flag = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
public static void main(String[] args) {
reader();
writer();
}
}
无volatile修饰flag的执行示意图:
public class WtyicyVolatile {
private volatile static boolean flag = true;
public static void reader() {
new Thread(() -> {
while (flag) {
}
}, "A").start();
}
public static void writer() {
new Thread(() -> {
// 避免 B 线程比 A 线程先运行修改 flag 值
try {
TimeUnit.SECONDS.sleep(1);
flag = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
public static void main(String[] args) {
reader();
writer();
}
}
volatile修饰flag的执行示意图:
3、有序性
volatile 包含禁止指令重排的语义,Java 内存模型会限制编译器重排序和处理器重排序,重排序的目的是编译器和处理器为了优化程序性能而对指令序列进行重排序,但在单线程和单处理器中,重排序不会改变有数据依赖关系的两个操作顺序。
public class ReorderDemo {
static int a = 0;
static int b = 0;
public static void main(String[] args) {
a = 2;
b = 3;
}
}
// 再多线程环境下,不影响结果的并且两个数据没有依赖,就允许重排
public class ReorderDemo {
static int a = 0;
static int b = 0;
public static void main(String[] args) {
b = 3; // a 和 b 重排序后,调换了位置
a = 2;
}
}
public class ReorderDemo {
static int a = 0;
static int b = 0;
public static void main(String[] args) {
// 由于 a 和 b 存在数据依赖关系,则不会进行重排序
a = 2;
b = a;
}
}
" NO " 表示禁止重排序
Java 内存模型在指令序列中插入内存屏障来处理 volatile 重排序规则,策略如下:
- volatile 写操作前插入一个 StoreStore 屏障
- volatile 写操作后插入一个 StoreLoad 屏障
- volatile 读操作后插入一个 LoadLoad 屏障,
- volatile 读操作后插入一个 LoadStore 屏障
该四种屏障意义:
- StoreStore:在该屏障后的写操作执行之前,保证该屏障前的写操作已刷新到主内存。
- StoreLoad:在该屏障后的读取操作执行之前,保证该屏障前的写操作已刷新到主内存。
- LoadLoad:在该屏障后的读取操作执行之前,保证该屏障前的读操作已读取完毕。
- LoadStore:在该屏障后的写操作执行之前,保证该屏障前的读操作已读取完毕。
volatile写是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障。
4、volatile的happens-before关系
著作权归https://pdai.tech所有。
链接:https://www.pdai.tech/md/java/thread/java-thread-x-key-volatile.html
//假设线程A执行writer方法,线程B执行reader方法
class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // 1 线程A修改共享变量
flag = true; // 2 线程A写volatile变量
}
public void reader() {
if (flag) { // 3 线程B读同一个volatile变量
int i = a; // 4 线程B读共享变量
}
}
}
根据 happens-before 规则,上面过程会建立 3 类 happens-before 关系。
- 根据程序次序规则:1 happens-before 2 且 3 happens-before 4。 (黑线)
- 根据 volatile 规则:2 happens-before 3。 (红线)
- 根据 happens-before 的传递性规则:1 happens-before 4。(蓝线)
5、往期佳文
5.1、面试系列
1、吊打面试官之一面自我介绍
2、吊打面试官之一面项目介绍
3、吊打面试官之一面系统架构设计
4、吊打面试官之一面你负责哪一块
5、吊打面试官之一面试官提问
6、吊打面试官之一面你有什么问题吗
······持续更新中······
5.2、技术系列
1、吊打面试官之分布式会话
2、吊打面试官之分布式锁
3、吊打面试官之乐观锁
4、吊打面试官之幂等性问题
5、吊打面试关之分布式事务
6、吊打面试官之项目线上问题排查
······持续更新中······
5.3、源码系列
1、源码分析之SpringBoot启动流程原理
2、源码分析之SpringBoot自动装配原理
3、源码分析之ArrayList容器
4、源码分析之LinkedList容器
5、码分析之HashMap容器
5.4、数据结构和算法系列
1、数据结构之八大数据结构
2、数据结构之动态查找树(二叉查找树,平衡二叉树,红黑树)
······持续更新中······
5.5、多线程系列
1、并发系列之初识多线程
2、并发系列之JMM内存模型
3、并发系列之synchronized解析
······持续更新中······