JMM使用happens-before(发生之前)的概念来阐述操作之间的内存可见性。让共享变量的写操作能够被其他线程的读操作可见,避免可见性问题,它是可见性与有序性的一套规则总结。
为什么会有这个规则呢
如果程序开发中,仅靠sychronized和volatile关键字来保证原子性、可见性以及有序性,那么编写并发程序可能会显得十分麻烦。
程序的顺序性规则
按照顺序推演程序的执行结果,但是编译器未必一定会按照这个顺序编译,但是编译器保证结果一定==顺序推演的结果。
对变量默认值(0,false,null)的写,对其它线程对该变量的读可见
// 共享变量static int x;public static void main(String[] args) {// t1线程对共享变量的赋值操作new Thread(() -> {// 共享变量的写操作x = 10;}, "t1").start();// t2线程对共享变量的读取操作new Thread(() -> {// t2线程在t1线程之后,t1线程执行结果能够被t2线程可见System.out.println(x);}, "t2").start();}
10
监视器锁规则
如果一个操作加锁在另一个操作发生之前解锁,那么第一个操作的执行结果将被第二个操作可见。
synchronized锁住了obj,需要两个线程执行完毕后才解锁,而在两个线程解锁 obj 之前,对变量的写操作都是会按照顺序执行,变量的写操作将是第一个操作。而t2线程要读取共享变量,但t2线程会一直是被t1线程阻塞住,直到t1线程写操作完成后,互斥锁释放,第二个操作就是对变量的读,对于接下来对 obj 加锁的其它线程对该变量的读可见
public class HappensBeforeSequentialTest {// 共享变量static int x;static Object obj = new Object();public static void main(String[] args) {// t1线程对共享变量的赋值操作new Thread(()-> {// 保证原子性和可见性和有序性synchronized(obj) {// 共享变量的写操作x = 10;}},"t1").start();// t2线程对共享变量的读取操作new Thread(()-> {synchronized(obj) {// t2线程在t1线程之后,t1线程执行结果能够被t2线程可见System.out.println(x);}},"t2").start();}}
10
volatile规则
线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
volatile是把共享变量加到主内存中,所以能够被其他线程可见
public class HappensBeforeVolatileTest {// 共享变量volatile static int x;public static void main(String[] args) {// t1线程对共享变量的赋值操作new Thread(() -> {// 共享变量的写操作x = 10;}, "t1").start();// t2线程对共享变量的读取操作new Thread(() -> {// t2线程在t1线程之后,t1线程执行结果能够被t2线程可见System.out.println(x);}, "t2").start();}}
10
线程启动规则
主线程mian启动线程t2,线程t2中可以看到主线程启动t2之前的操作。
线程 start 前对变量的写,对该线程开始后对该变量的读可见
// 共享变量static int x;public static void main(String[] args) {// 共享变量的写操作x = 10;// t2线程对共享变量的读取操作new Thread(() -> {// t2线程在t1线程之后,t1线程执行结果能够被t2线程可见System.out.println(x);}, "t2").start();}
10
线程终结规则
主线程main等待子线程t1完成,当子线程t1执行完毕后,主线程main可以看到线程B的所有操作。
线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用 t1.isAlive() 或 t1.join()等待它结束)
// 共享变量static int x;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {// 共享变量的写操作x = 10;}, "t1");t1.start();t1.join();System.out.println(x);}
10
线程中断规则
线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过 t2.interrupted 或 t2.isInterrupted)
// 共享变量static int x;public static void main(String[] args) throws InterruptedException {Thread t2 = new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.println(x);break;}}}, "t2");t2.start();new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}x = 10;t2.interrupt();}, "t1").start();while (!t2.isInterrupted()) {Thread.yield();}System.out.println(x);}
1010
传递性规则
具有传递性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z ,配合 volatile 的防指令重排,有下面的例子
// 共享变量volatile static int x;static int y;public static void main(String[] args) throws InterruptedException {new Thread(()->{y = 10;x = 20;},"t1").start();new Thread(()->{// x=20 对 t2 可见, 同时 y=10 也对 t2 可见System.out.println(x);},"t2").start();}
20
