多线程开发下可能出现的问题及解决方案

本文详细探讨了多线程开发中可能出现的问题,如线程执行顺序导致的变量竞态条件,重点介绍了volatile关键字的作用以及它在内存屏障上的应用。通过实例和内存屏障原理,解释了如何确保单例模式和其他共享变量的线程安全。
摘要由CSDN通过智能技术生成

多线程开发下可能出现的问题及解决方案

int a;
int b;
@Actor
public void actor1(II_Result r) {
b = 1;
r.r2 = a;
}
@Actor
public void actor2(II_Result r) {
a = 2;
r.r1 = b;
}

上述代码根据线程的执行顺序,可能会有四种情况

b = 1; // 线程1
r.r2 = a; // 线程1
a = 2; // 线程2
r.r1 = b; // 线程2
// 结果 r1==1, r2==0
a = 2; // 线程2
r.r1 = b; // 线程2
b = 1; // 线程1
r.r2 = a; // 线程1
// 结果 r1==0, r2==2
a = 2; // 线程2
b = 1; // 线程1
r.r2 = a; // 线程1
r.r1 = b; // 线程2
// 结果 r1==1, r2==2
r.r2 = a; // 线程1
a = 2; // 线程2
r.r1 = b; // 线程2
b = 1; // 线程1
// 结果 r1==0, r2==0

第四种情况是需要用压测工具才能测出来的,在同个方法下,执行的代码可能会进行重排序的结果。
原因来自编译器和硬件层面都做了自优化:
1.Compiler/JIT 优化
2.Processor 流水线优化
3.Cache 缓存优化

为了解决上述的问题,实际上可用的方法很多,比如同步代码块,锁机制等等
这里着重说一下volatile关键字


int x;
volatile int y;
@Actor
public void a1(II_Result r) {
y = 1; //1 处
r.r2 = x; //2 处
}
@Actor
public void a2(II_Result r) {
x = 1; //3 处
r.r1 = y; //4 处
}

//1 //2 处的顺序可以保证(只写了 volatile 变量),但 //3 //4 处的顺序却不能保证(只读了 volatile 变量),仍
会出现 r1== r2==0 的问题

@Actor
public void a1(II_Result r) {
r.r2 = x; //1 处
y = 1; //2 处
}
@Actor
public void a2(II_Result r) {
r.r1 = y; //3 处
x = 1; //4 处
}

这回 //1 //2 (只写了 volatile 变量)//3 //4 处(只读了 volatile 变量)的顺序均能保证了,绝不会出现
r1== r2==1 的情况

出现的原因与cpu的架构有关,里面存在四种内存屏障

LoadLoad

防止 B 的 Load 重排到 A 的 Load 之前

if(A) {
LoadLoad
return B
}
read(A)
LoadLoad
read(B)

意义:A == true 时,再去获取 B,否则可能会由于重排导致 B 的值相对于 A 是过期的。

LoadStore

防止 B 的 Store 被重排到 A 的 Load 之前

StoreStore

防止 A 的 Store 被重排到 B 的 Store 之后

StoreLoad(*)

意义:屏障前的改动都同步到主存1,屏障后的 Load 获取主存最新数据
防止屏障前所有的写操作,被重排序到屏障后的任何的读操作,可以认为此 store -> load 是连续的
有点类似于 git 中先 commit,再远程 pull,而且这个动作是原子的

如何记忆使用

LoadLoad + LoadStore = Acquire 即让同一线程内读操作之后的读写上不去,第一个 Load 能读到主存最新值
LoadStore + StoreStore = Release 即让同一线程内写操作之前的读写下不来,后一个 Store 能将改动都写入主存
在这里插入图片描述

同理,在做单例的时候,当创建了一个对象,并把它赋值给共享变量时,这个存在线程安全问题

在这里插入图片描述

不加finally修饰:
在这里插入图片描述
加了finally:
在这里插入图片描述

使用 volatile 改进

age 有 volatile 修饰,注意位置必须在最后
在这里插入图片描述

常规的单例写法:

public class Singleton {
private static Singleton INSTANCE = null;
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}

在实例前面加上volatile修饰可以保证线程安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值