目录
- 5.1 Java的内存模型
- 5.2 可见性
- 1、退不出的循环
- 2、解决办法:
- 3、可见性 vs 原子性
- 5.3 有序性
- 1、为什么会有指令重排
- 2、如何禁止指令重排
- ==3、原理之volatile==
- 4、happens-before
5.1 Java的内存模型
JMM 即 Java Memory Model(Java内存模型),它定义了主存、工作内存抽象概念
5.2 可见性
1、退不出的循环
现象:
main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止:
@Slf4j(topic = "c.Test32")
public class Test32 {
// 易变
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(true){
if(!run) {
break;
}
}
});
t.start();
sleep(1);
run = false; // 线程t不会如预想的停下来
//System.out.println("run=false");
}
}
分析:
初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存
因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
2、解决办法:
volatile(易变关键字)
它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
3、可见性 vs 原子性
可见性保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见,volatile 不能保证原子性
原子性指的是在某个时刻保证只有一个线程执行临界区的代码
【注意】
synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized 是属于重量级操作,性能相对更低
【扩展】
如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到对 run 变量的修改了
原因: 之后再进行补充
5.3 有序性
JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,这种特性称之为==『指令重排』==,多线程下『指令重排』会影响正确性
1、为什么会有指令重排
从指令集并行来解释为什么需要指令重排:
2、如何禁止指令重排
volatile 修饰的变量,可以禁用指令重排
3、原理之volatile
4、happens-before
happens-before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结
- 线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见
static int x;
static Object m = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (m) {
log.info("t1线程给共享变量x赋值为10");
x = 10;
}
}, "t1").start();
new Thread(() -> {
synchronized (m) {
log.info("t2线程读取共享变量x");
System.out.println(x);
}
}, "t2").start();
}
- 线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
volatile static int x;
public static void main(String[] args) {
new Thread(() -> {
x = 10;
}, "t1").start();
new Thread(() -> {
System.out.println(x);
}, "t2").start();
}
- 线程 start 前对变量的写,对该线程开始后对该变量的读可见
static int x;
x = 10;
new Thread(()->{
System.out.println(x);
},"t2").start();
- 线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用t1.join()等待它结束)
static int x;
Thread t1 = new Thread(()->{
x = 10;
},"t1");
t1.start();
t1.join();
System.out.println(x);
-
线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过t2.interrupted 或 t2.isInterrupted)
-
对变量默认值(0,false,null)的写,对其它线程对该变量的读可见