当前位置:网站首页>内存模型之有序性
内存模型之有序性
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 之间的可见性与有序性
边栏推荐
猜你喜欢
Eject stubborn hard drives with diskpart's offline command
LeetCode 264:丑数
AI mid-stage sequence labeling task: three data set construction process records
The Transformer, BERT, GPT paper intensive reading notes
【图像边缘检测】基于matlab灰度图像的积累加权边缘检测【含Matlab源码 2010期】
Docker starts mysql
如何使用电子邮件营销在五个步骤中增加产品评论
xshell开启ssh端口转发,通过公网机器访问内网机器
用云机器/虚拟机架设方舟游戏?
How to choose a reliable and formal training institution for the exam in September?
随机推荐
Dapr 与 NestJs ,实战编写一个 Pub & Sub 装饰器
Evaluate: A detailed introduction to the introduction of huggingface evaluation indicator module
控制bean的加载
mysql系统变量与状态变量
ArcEngine (six) use the tool tool to realize the zoom in, zoom out and translation of the pull box
ceph简介
[ 漏洞复现篇 ] yapi 代码执行 getshell 漏洞复现详解
ArcEngine(五)用ICommand接口实现放大缩小
The use of the database table structure document generation tool screw
ArcEngine(一)加载矢量数据
千万级别的表分页查询非常慢,怎么办?
请求与响应:响应
Evaluate:huggingface评价指标模块入门详细介绍
Logic Pro X built-in sound library list
Nanny level explains Transformer
热部署系统实现
STL-vector容器
thop 使用心得
标准输入流
mysql的innodb存储引擎和myisam存储引擎的区别