1 JMM内存模型
由于存在编译器优化、Processor流水线优化、缓存优化等,我们编写的代码不一定是实际运行的代码,我们编写的代码顺序不一定是实际执行的顺序,会出现可见性、原子性、有序性问题。所以我们需要学习JMM内存模型来解决上面的问题。
1.1 什么是JMM
内存模型就是多线程下对共享变量的一组读写规则。
- 共享变量值是否在线程间同步
- 代码可能的执行顺序
- 需要关注的操作就有两种 Load、Store
- Load 就是从缓存读取到寄存器中,如果一级缓存中没有,就会层层读取二级、三级缓存,最后才是Memory
- Store 就是从寄存器运算结果写入缓存,不会直接写入 Memory,当 Cache line 将被 eject 时,会
writeback 到 Memory
1.2 JMM规范
1.2.1 规则1 - Race Condition
在多线程下,没有依赖关系的代码,在执行共享变量读写操作(至少有一个线程写)时,并不能保证以编写顺序(Program Order)执行,这称为发生了竞态条件(Race Condition)。
例如:
有共享变量x、y,线程1执行
r.r1 = y;
r.r2 = x;
线程2执行
x = 1;
y = 1;
最终的结果可能是 r1==1 而 r2==0
y = 1;
r.r1 = y;
r.r2 = x;
x = 1;
1.2.2 规则2 - Synchronization Order
若要保证多线程下,每个线程的执行顺序(Synchronization Order)按编写顺序(Program Order)执行,那么必须使用 Synchronization Actions(同步动作) 来保证,这些 SA 有
- lock,unlock - synchronized, ReentrantLock
- volatile 方式读写变量 - 保证可见性,防止重排序
- VarHandle 方式读写变量
例如:
用 volatile 修饰共享变量 y,线程 1 执行
r.r1 = y;
r.r2 = x;
线程 2 执行
x = 1;
y = 1;
最终的结果就不可能是 r1==1 而 r2==0
1.2.3 规则3 - Happens-Before
线程切换时代码的顺序和可见性
若是变量读写时发生线程切换(例如,线程 1 写入 x,切换至线程 2,线程 2 读取 x)在这些边界的处理上如果有action1 先于 action 2 发生,那么代码可以按确定的顺序执行,这称之为 Happens-Before Order 规则。
用公式表达为
含义为:如果 action1 先于 action2 发生,那么 action1 之前的共享变量的修改对于 action2 可见,且代码按顺序执行。
具体规则
其中 代表线程,而 x 未加说明,是普通共享变量,使用 volatile 会单独说明
1)线程的启动和运行边界
2)线程的结束和 join 边界
3)线程的打断和得知打断边界
4)unlock 与 lock 边界