多线程开发下可能出现的问题及解决方案
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修饰可以保证线程安全