Happens-before原则
JMM的定义是通过动作的形式来描述的,所谓动作,包括变量的读和写,监视器加锁和释放锁,线程的启动和拼接,这就是传说中的happen before,要想A动作看到B动作的结果,B和A必须满足happen before关系。
是一种可见性规则,它表达的含义是前面一个操作的结 果对后续操作是可见的,是对编译器和处理器重排序的规则
Happens-before规则
从JDK5开始,java使用新的JSR -133内存模型(本文除非特别说明,针对的都是JSR- 133内存模型)。JSR-133提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。 与程序员密切相关的happens-before规则如下:
- 程序顺序规则:在一个单独的线程中,按照程序代码的执行流顺序,(时间上)先执行的操作happen—before(时间上)后执行的操作。
- 监视器锁规则:一个unlock操作happen—before后面(时间上的先后顺序,下同)对同一个锁的lock操作。
- volatile变量规则:对一个volatile变量的写操作happen—before后面对该变量的读操作
- 传递性规则:如果A happens-before于B,B happens-before于C,那么A happens-before于C
- 线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。(扩:假如线程A执行中调用B.start(),线程A对共享变量修改B知道)
- 线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。(扩:线程A写入的变量,调用Thread.interrupt(),被打断的线程B可以看到A的操作)
- 线程终结规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。(线程A写入的变量,在任意线程B调用A.join()或A.isALive()成功后,对B可见)
- 对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。
注意,两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second)。这样的重排序也是可以的
happens-before与JMM的关系
如上图所示,一个happens-before规则通常对应于多个编译器重排序规则和处理器重排序规则。对于java程序员来说,happens-before规则简单易懂,它避免程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现。
举例:
程序顺序规则(as-if-serial语义)
- 不能改变程序的执行结果(在单线程环境下,执行的结果不变.)
- 依赖问题, 如果两个指令存在依赖关系,是不允许重排序
int a=0;
int b=0;
void test(){
int a=1; a
int b=1; b
//int b=1;
//int a=1;
int c=a*b; c
}
a happens -before b ; b happens before c
传递性规则
a happens-before b , b happens- before c, a happens-before c
volatile变量规则
volatile 修饰的变量的写操作,一定happens-before后续对于volatile变量的读操作. 内存屏障机制来防止指令重排.
public class VolatileExample{
int a=0;
volatile boolean flag=false;
public void writer(){
a=1; 1
flag=true; //修改 2
}
public void reader(){
if(flag){ //true 3
int i=a; //1 4
}
}
}
1 happens-before 2 是否成立? 是 -> ?
3 happens-before 4 是否成立? 是
2 happens -before 3 ->volatile规则
1 happens-before 4 ; i=1成立.
监视器锁规则
对一个锁的解锁,happens-before 于 随后对这个锁的加锁
int x=10;
synchronized(this){
// x 是共享变量, 初始值 =10
//后续线程读取到的x的值一定12
if(x<12){
x=12;
}
}
x=12;
假设 x 的初始值是 10,线程 A 执行完代码块后 x 的 值会变成 12(执行完自动释放锁),线程 B 进入代码块 时,能够看到线程 A 对 x 的写操作,也就是线程 B 能 够看到 x==12。
start规则
如果线程 A 执行操作 ThreadB.start(),那么线 程 A 的 ThreadB.start()操作 happens-before 线程 B 中 的任意操作
public class StartDemo{
int x=0;
Thread t1=new Thread(()->{
// 主线程调用 t1.start() 之前
// 所有对共享变量的修改,此处皆可见
//读取x的值 一定是20
if(x==20){
}
});
x=20;
t1.start();
}
Join规则
如果线程 A 执行操作 ThreadB.join()并成功返 回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作成功返回。
public class Test{
int x=0;
Thread t1=new Thread(()->{
// 此处对共享变量 x 修改
x=200;
});
t1.start();
t1.join(); //保证结果的可见性。
// 子线程所有对共享变量的修改
// 在主线程调用 t1.join() 之后皆可见
//在此处读取到的x的值一定是200.
}