happens-before规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下happens-before规则,JMM并不能保证一个线程对共享变量的写,其它线程对该共享变量的读可见
线程对共享变量访问时,如果将sychronized加在共享变量之外,那么能够保证变量的可见性 ```java public class Test {
static int x; static Object m = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (m){
x=10;
}
},"t1").start();
new Thread(()->{
synchronized(m){
System.out.println(x);
}
},"t2").start();
}
}
对共享变量的访问如 x=10; System.out.println(x) 套在synchronized之中,可以保证对变量x访问的可见性。
- 线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
```java
public class Test {
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 前对变量的写,该线程开始后对该变量的读可见 ```java public class Test {
volatile static int x;
public static void main(String[] args) {
x=10;
new Thread(()->{
System.out.println(x);
},"t2").start();
} }
- 线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用t1.Alive() 或 t1.join()等待它结束)
```java
public class Test {
volatile 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);
}
}
线程已经结束,那么x=10一定是写回到了主存中。
- 对变量默认值(0,false,null)的写,对其它线程对该变量的读可见
具有传递性,配合volatile的防重排指令,有如下例子 ```java public class Test {
volatile static int x; static int y;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
y=10;
x=20;
},"t1");
t1.start();
new Thread(()->{
System.out.println(x);
System.out.println(y);
},"t2").start();
} }
``` volatile 修饰x变量,x=20相当于写,会加入写屏障,写屏障前的所有赋值均会写进主内存中,重要的是,当x被volatile修饰后,线程t2再读x值,一定是从主存中读取而不是从缓存/工作内存中读取。
注意:
除了synchronized修饰、volatile修饰的两种情况,接下来的几种情况均是因为System.out.println()方法本身保证了从主内存中读取值。
所以除了synchronized修饰、volatile修饰能够保证从主内存中读值,其它情况应该是错误的,因为读值还是可能从cache中读取,要擦亮双眼。