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