当前位置:网站首页>内存模型之有序性
内存模型之有序性
2022-08-03 07:48:00 【七国的天下,我要九十九】
1 引入
在代码中,JVM 会在不影响正确性的前提下,可以调整语句的执行顺序.
如:
static int i;
static int j;
// 在某个线程内执行如下赋值操作
i = ...;
j = ...;
先执行 i 还是 先执行 j ,对最终的结果不会产生影响所以执行方式可能为
i = ...;
j = ...;
也可能为
j = ...;
i = ...;
即称为,指令重排
2 案例
int num = 0;
boolean ready = false;
// 线程1 执行此方法
public void actor1(I_Result r) {
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
// 线程2 执行此方法
public void actor2(I_Result r) {
num = 2;
ready = true;
}
分析:
- 情况1 线程1 先执行,这时 ready = false,所以进入 else 分支结果为 1
- 情况2 线程2 先执行 num = 2,但没来得及执行 ready = true,线程1 执行,还是进入 else 分支,结果为1
- 情况3 线程2 执行到 ready = true,线程1 执行,这回进入 if 分支,结果为 4(因为 num 已经执行过了)
- 情况4 线程2 执行 ready = true,切换到线程1,进入 if 分支,相加为 0,再切回线程2 执行 num = 2
指令重排,是 JIT 编译器在运行时的一些优化,这个现象需要通过大量测试才能复现
可借助Java并发压测工具jcstress工具
解决办法
volatile 修饰的变量,可以禁用指令重排.
3 volatile原理
volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对 volatile 变量的写指令后会加入写屏障
- 对 volatile 变量的读指令前会加入读屏障
1 保证可见性
写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor2(I_Result r) {
num = 2;
ready = true; // ready 是 volatile 赋值带写屏障
// 写屏障
}
读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {
// 读屏障
// ready 是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}

2 保证有序性
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
public void actor2(I_Result r) {
num = 2;
// 写屏障
// ready 是 volatile 赋值带写屏障
ready = true;
}
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {
// 读屏障
// ready 是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}

但是不能解决指令交错问题.
- 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
- 有序性的保证也只是保证了本线程内相关代码不被重排序
3 dcl问题
即double-checked locking 单例模式
public final class Singleton {
private Singleton() {
}
private static Singleton INSTANCE = null;
public static Singleton getInstance() {
if(INSTANCE == null) {
// t2
// 首次访问会同步,而之后的使用没有 synchronized
synchronized(Singleton.class) {
if (INSTANCE == null) {
// t1
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
说明:
- 1 懒惰实例化
- 2 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁
- 3 第一个 if 使用了 INSTANCE 变量,是在同步块之外
在多线程环境下,代码可能存在问题. 查看对应字节码:
0: getstatic #2 // Field INSTANCE:Lcn/cf/n5/Singleton;
3: ifnonnull 37
6: ldc #3 // class cn/cf/n5/Singleton
8: dup
9: astore_0
10: monitorenter
11: getstatic #2 // Field INSTANCE:Lcn/cf/n5/Singleton;
14: ifnonnull 27
17: new #3 // class cn/cf/n5/Singleton
20: dup
21: invokespecial #4 // Method "<init>":()V
24: putstatic #2 // Field INSTANCE:Lcn/cf/n5/Singleton;
27: aload_0
28: monitorexit
29: goto 37
32: astore_1
33: aload_0
34: monitorexit
35: aload_1
36: athrow
37: getstatic #2 // Field INSTANCE:Lcn/cf/n5/Singleton;
40: areturn
说明:
- 17 表示创建对象,将对象引用入栈 // new Singleton
- 20 表示复制一份对象引用 // 引用地址
- 21 表示利用一个对象引用,调用构造方法
- 24 表示利用一个对象引用,赋值给 static INSTANCE
可能 jvm 会优化为:先执行 24,再执行 21。如果两个线程 t1,t2 按如下时间序列执行:

在0: getstatic 这行代码在 monitor 控制之外,它就像之前举例中不守规则的人,可以越过 monitor 读取 INSTANCE 变量的值, 此时 t1 还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么 t2 拿到的是将是一个未初 始化完毕的单例.(存在问题)
4 dcl问题解决
使用 volatile 修饰即可,可以禁用指令重排
public final class Singleton {
private Singleton() {
}
private static volatile Singleton INSTANCE = null;
public static Singleton getInstance() {
// 实例没创建,才会进入内部的 synchronized代码块
if (INSTANCE == null) {
synchronized (Singleton.class) {
// t2
// 也许有其它线程已经创建实例,所以再判断一次
if (INSTANCE == null) {
// t1
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
读写 volatile 变量时会加入内存屏障(Memory Barrier(Memory Fence)),保证下面 两点:
- 可见性
- 写屏障(sfence)保证在该屏障之前的 t1 对共享变量的改动,都同步到主存当中
- 读屏障(lfence)保证在该屏障之后 t2 对共享变量的读取,加载的是主存中最新数据
- 有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
更底层是读写变量时使用 lock 指令来多核 CPU 之间的可见性与有序性

边栏推荐
猜你喜欢
随机推荐
数据监控平台
热部署系统实现
Logic Pro X自带音色库列表
The use of the database table structure document generation tool screw
如何在安装GBase 8c数据库的时候,报错显示“Host ips belong to different cluster?
Oracle Rac Cluster File Directory Migration
ArcEngine(五)用ICommand接口实现放大缩小
Nacos使用实践
WordPress主题-B2美化通用子主题商业运营版
Neo4j 4.X:导入OWL文件
mysql存生僻字奇怪问题,mysql为什么不能辨别mb4字符?
22-08-02 西安 尚医通(02)Vscode、ES6、nodejs、npm、Bable转码器
训练正常&异常的GAN损失函数loss变化应该是怎么样的
[Hello World] 二分查找笔记
How to choose a reliable and formal training institution for the exam in September?
Qt5开发从入门到精通——第二篇(控件篇)
加速FinOps实践,为企业降本增效
mysql备份时的快照原理
Roson的Qt之旅#104 QML Image控件
依赖注入(DI),自动配置,集合注入









