INDEX
并发编程
并发编程的特性
先行发生原则(Happens-Before)
- 若 java 内存中,操作 A 先行发生于 操作 B ,则 操作 B 可以观测到 操作 A 产生的影响
- 用于判断数据是否存在线程竞争或是否线程安全
java 语法天然支持下面 8 个先行发生场景
下面8个场景中,操作 A 先行发生于操作 B 时,操作 B 可以观测到 操作 A 的结果
- 程序次序规则(Program Order Rule)
同一个线程内,流程控制上在前的操作先行发生于流程控制中在后的操作时 - 管程锁定规则(Monitor Lock Rule)
同一个锁的 unlock 操作 先行发生于 此锁的 lock 操作时 - volatile 变量规则(Volatile Variable Rule)
同一个 volatile 变量的写操作 先行发生于对于 这个变量的读操作之前时 - 线程启动规则(Thread Start Rule)
同一个线程 start()方法 先行发生 此线程其他方法时 - 线程终止规则(Thread Termination Rule)
同一个线程的 所有其他操作 先行发生 此线程的终止检测(join() 和 isAlive())时 - 线程中断规则(Thread Interruption Rule)
同一个线程的 interrupt() 方法的调用先行发生 此线程的终止检测(interrupt())时 - 对象终结规则(Finalizer Rule)
同一个对象的 构造方法执行结束 先行发生于 此对象 finalize() 方法开始时 - 传递性(transitivity)
若 操作A 先行发生于 操作B
且 操作B 先行发生与 操作C
则 操作A 先行发生于 操作C
volatile 关键字
volatile 关键字是 java 虚拟机提供的轻量级(线程间)同步机制,这意味着 volatile
- 保证可见性
- 不保证原子性
- 禁用指令重排
指令重排
为提高性能,编译器和处理器会对指令进行重排
包括编译器优化重排、指令并行重排、内存重排
单线程时,指令重排前后保持一致
多线程时,可能出现问题
多线程下指令重排问题示例
说明
假设只有一个 AAA 对象
有两个线程,线程 1 调用 m1,线程 2 调用 m2
此测试程序编译并执行多次,
每次都是 m1 的第一句执行后,m2 开始执行
则
m1 先执行 a=1 时,输出 6
m1 先执行 f=true 时,输出 5
class AAA{
int a = 0;
boolean f = false;
public void m1(){
a = 1;
f = true;
}
public void m2(){
if (f){
a = a+5;
System.out.println(a);
}
}
}
内存屏障(Memory Barrier)
内存屏障是一个 cpu 指令,作用如下
- 内存屏障前后的指令不能与之调换顺序,因此前后指令也不会调换顺序
- 内存屏障可以强制刷新 cpu 的缓存数据
JVM 指令重排的规定
是否允许指令重排 | 后 普通读 | 后 普通写 | 后 volatile 读 | 后 volatile 写 |
---|---|---|---|---|
先 普通读 | √ | √ | √ | × |
先 普通写 | √ | √ | √ | × |
先 volatile 读 | × | × | × | × |
先 volatile 写 | √ | √ | × | × |
概括的讲,下面场景禁用指令重排
- 先 volatile 读时
- 后 volatile 写时
- 先 volatile 写后读时
JMM 内存屏障策略
JMM 在 volatile 读写前后添加屏障如下面两组
volatile 写组
- 写写屏障(StoreStore)
- volatile 写
- 写读屏障(StoreLoad)
volatile 读组
- volatile 读
- 读读屏障(LoadLoad)
- 读写屏障(LoadStore)
上面两组直接与普通读写自由前后组合,如先 volatile 读,后 volatile 写可以等效为
- volatile 读
- 读读屏障(LoadLoad)
- 读写屏障(LoadStore)
- 写写屏障(StoreStore)
- volatile 写
- 写读屏障(StoreLoad)
是否允许指令重排 | 后 普通读 | 后 普通写 | 后 volatile 读 | 后 volatile 写 |
---|---|---|---|---|
先 普通读 | - | - | - | 写写屏障(StoreStore) |
先 普通写 | - | - | - | 写写屏障(StoreStore) |
先 volatile 读 | 读读屏障(LoadLoad) | 读写屏障(LoadStore) | 读读屏障(LoadLoad) | 写写屏障(StoreStore |
先 volatile 写 | - | - | 写读屏障(StoreLoad) | 写读屏障(StoreLoad) |
volatile 的适用场景
单例
双重检验懒汉式(DCL)理论上有很小概率出现问题
语句 instance = new Singleton() 不是原子的
由 3 步组成
- 在堆中分配内存空间
- 初始化对象
- 变量 instance 指向内存空间
若 2 、3 步发生指令重排,且出现并发问题
则因为引用了内存空间,内外两层校验检查时 != null
但此时返回的是一个未初始化完成的对象
可以使用 volatile 禁用指令重排
读写锁缓存
JUC 包中