Java 内存模型
可见性 & 原子性 & 有序性
可见性保证:指令不会受 cpu 缓存的影响
可见性指:当一个线程修改了某一个共享变量的值,其他的线程是否能够立即知道这个修改。
原子性保证:指令不会受到线程上下文切换的影响
原子性指:一个操作是不可中断的。
即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
使用关键字 synchronized 来保证:代码块内的操作是原子的
有序性保证指令不会受 cpu 指令并行优化的影响
JVM 会在不影响正确性的前提下,可以调整语句的执行顺序。这种特性称之为『指令重排』,多线程下『指令重排』会影响正确性。
volatile
易变关键字。
它用来修饰成员变量和静态成员变量(放在主存中的变量)。
它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,保证了可见性。
volatile 修饰的变量,可以禁用指令重排。 禁止的是加 volatile 关键字变量之前的代码被重排序。
内存屏障
volatile的底层实现原理是 内存屏障,Memory Barrier (Memory Fence)
- 对 volatile 变量的写指令后会加入写屏障
- 对 volatile 变量的读指令前会加入读屏障
可见性
- 写屏障 (sfence) 保证在该屏障之前的,对共享变量的改动,都同步到主存中
- 读屏障 (lfence) 保证在该屏障之后的,对共享变量的读取,加载的是主存中的数据
有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
但是不能解决指令交错问题
- 写屏障仅仅是保证之后的读能够读到新的结果,但不能保证读跑到它前面去
- 而有序性也只是保证了本线程内相关代码不被重排序
happens - before 规则
happens - before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下 happens - before 规则,JMM 并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见变量都是指成员变量或静态成员变量
线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见
static int x;static Object m = new Object();new Thread(() -> {synchronized (m) {x = 10;}}, "t1").start();new Thread(() -> {synchronized (m) {System.out.println(x);}}, "t2").start();
线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
volatile static int x;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.isAlive() 或 t1.join()等待它结束)
static int x;Thread t1 = new Thread(()->{x = 10;},"t1").start();t1.join();System.out.println(x);
线程 t1 打断 t2 (interrupt) 前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过 t2.interrupted 或 t2.isInterrupted)
static int x;public static void main(String[] args) {Thread t2 = new Thread(()->{while(true) {if(Thread.currentThread().isInterrupted()) {log.debug("{}",x);break;}}},"t2").start();new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}x = 10;t2.interrupt();},"t1").start();while(!t2.isInterrupted()) {Thread.yield();}log.debug("{}",x);}
对变量默认值(0,false,null)的写,对其它线程对该变量的读可见
具有传递性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z ,配合 volatile 的防指令重排,有下面的例子
volatile static int x;static int y;public static void main(String[] args) {new Thread(() -> {y = 10;x = 20;}, "t1").start();new Thread(() -> {// x=20 对 t2 可见, 同时 y=10 也对 t2 可见System.out.println(x);}, "t2").start();}
