当前位置:网站首页>内存模型之有序性
内存模型之有序性
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 之间的可见性与有序性

边栏推荐
- 如何让背景色在任何设备宽高都能填充整个屏幕
- 实时目标检测新高地之#YOLOv7#更快更强的目标检测器
- boot-SSE
- Evaluate: A detailed introduction to the introduction of huggingface evaluation indicator module
- Roson的Qt之旅#104 QML Image控件
- netstat 及 ifconfig 是如何工作的。
- drop database出现1010
- 控制bean的加载
- ArcEngine (5) use the ICommand interface to achieve zoom in and zoom out
- 前缀和(区间和,子矩阵的和)
猜你喜欢

【第1天】SQL快速入门-基础查询(SQL 小虚竹)

How to choose a reliable and formal training institution for the exam in September?

数据监控平台

LeetCode 264:丑数

控制bean的加载

The use of the database table structure document generation tool screw

REST学习
![[Kaggle combat] Prediction of the number of survivors of the Titanic (from zero to submission to Kaggle to model saving and restoration)](/img/2b/d2f565d9221da094a9ccc30f506dc8.png)
[Kaggle combat] Prediction of the number of survivors of the Titanic (from zero to submission to Kaggle to model saving and restoration)

pyspark df secondary sorting

Arduino框架下对ESP32 NVS非易失性存储解读以及应用示例
随机推荐
Pop Harmony Basics Big Notes
mysql存生僻字奇怪问题,mysql为什么不能辨别mb4字符?
加载properties文件,容器总结
PostMan使用,访问路径@RequestMapping
Arduino框架下对ESP32 NVS非易失性存储解读以及应用示例
mysql5.7服务器The innodb_system data file 'ibdata1' must be writable导致无法启动服务器
vs 2022无法安装 vc_runtimeMinmum_x86错误
STL-vector容器
Poke the myth of Web3?Poke the iron plate.
tolower函数
36氪详情页AES
如何在安装GBase 8c数据库的时候,报错显示“Host ips belong to different cluster?
Evaluate: A detailed introduction to the introduction of huggingface evaluation indicator module
[ 漏洞复现篇 ] yapi 代码执行 getshell 漏洞复现详解
DSP Trick:向量长度估算
“碳中和”愿景下,什么样的数据中心才是我们需要的?
审批流设计
Fortify白盒神器20.1.1下载及安装(非百度网盘)
[Kaggle combat] Prediction of the number of survivors of the Titanic (from zero to submission to Kaggle to model saving and restoration)
Shell运维开发基础(一)