该栏目讲叙多线程基础、共享模型的内存、管程、无锁、不可变设计和多线程并发工具
Java 内存模型
概述
:JMM 是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题特点
- 原子性 - 保证指令不会受到线程上下文切换的影响
- 可见性 - 保证指令不会受 cpu 缓存的影响
- 有序性 - 保证指令不会受 cpu 指令并行优化的影响
可见性
1、举例
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
sleep(1);
run = false; // 线程t不会如预想的停下来
}
2、分析
- 初始状态,T 线程刚开始从主内存读取了 run 的值到工作内存
- 因为 T 线程要频繁从主内存中读取run的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
- 1 秒之后,main 线程修改了 run 的值,并同步至主存,而 T 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
3、解决方法
- :volatile 可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
有序性
- :JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,添加 volatile 修饰的变量,可以禁用指令重排
happens-before 原则
- 线程解锁前对变量的写,对于接下来加锁的其它线程对该变量的读可见
- 线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
- 线程 start 前对变量的写,对该线程开始后对该变量的读可见
- 线程结束前对变量的写,对其它线程得知它结束后的读可见
- 线程 T1 打断 T2 前对变量的写,对于其他线程得知 T2 被打断后对变量的读可见
- 对变量默认值的写,对其它线程对该变量的读可见
- 具有传递性
volatile 原理
可见性
- 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
- 读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中新数据
有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前