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);
}
10
10
传递性规则
具有传递性,如果 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