happen-before原则解读

1.基本概述

1.1 为什么要happen-before原则

由于多线程编程时,线程并不直接操作主内存,所以会存在缓存不一致的问题(可见性问题)。为了解决这个问题,想要我们在编写程序的时候非常小心,而编写多线程程序时我们需要注意的地方就非常多。java设计者也考虑到这个问题,所以设计了happen-before原则,只要符合其中的规则,就不用担心可见性问题。

1.2 happen-before原则定义

如果操作A happen-before 操作B , 就认为操作A对操作B可见。

看这句话非常简单,其实是你想简单了,往下看就知道。😁

2.规则解读

2.1 程序顺序规则

定义:在同一个线程中,书写在前面的操作happen-before后面的操作。

// thread1
int a = 1; ----A
int b = 2; ----B

上面定义了A,B两个操作,通过定义我们可以知道A happen-before B,即A操作的结果对B可见
是不是很简单?这里其实会存在一个误区,A happen-before B就是指操作A时间上先于操作B,相信很多人都会这样理解,但这是错的。就以上面的例子来说,因为存在指令重排序的情况,在没有任何依赖的两个操作间,为了性能优化,CPU可能会交换其执行顺序,所以操作B也可能先于操作A。
那我们应该怎么理解happen-before呢?只要在每种规则基本条件满足的情况下(比如:同一个线程),感官上A先于B,并且就可以认为A happen-before B。java的设计者是为了简化多线程编程的复杂度,肯定不会需要你了解真正的底层实现,不然就本末倒置了。

2.2 监视器锁规则

定义:同一个锁的unlock操作happen-before此锁的lock操作

thread2(等待)      -------------------- B(lock)
void synchronized code(){
  // 临界区
}
thread1           -------------------- A(unlock)

这个规则就是为了保证锁能正常执行,不然thread2如果一直没有获取到unlock结果,不可能在往下执行了。

不过这里也有一个地方需要注意的,这个规则保证的是通过synchronized关键字加锁,而不是手动加锁(比如:使用ReentrantLock)。

2.3 volatile变量规则

定义:对一个volatile变量的写操作happen-before对此变量的任意操作。

// thread1
String jobName="job1";
volatile boolean stop = true; ------A


//thread2                     
while(!stop){                  ------B
   exec(jobName);
}

这个例子在实际项目中很容易看到,当我们需要将一个循环执行的线程停止时会通过修改启停标识实现。根据这个规则可知,当thread1执行完A操作之后,马上会跳出while循环。

思考题:假如这里的stop没有通过volatile修饰(即没有happen-before规则保证可见性)会出现什么情况?[在专门介绍volatile后再给答案,大家可以思考思考]

2.4 传递性规则

定义: 如果A happens-before B,且B happens-before C,那么A happens-before C。
为了说明这个规则,我们还是以上面的操作为例。

// thread1
String jobName="job1";           ------A
volatile boolean stop = true;    ------B


//thread2                     
while(!stop){                    ------C
   exec(jobName);                ------D
}

上面已经指出了每个操作的位置,现在我需要大家证明一下:A happen-before D(即:jobName在没有添加volatile修饰的情况下也能保证跨线程的可见性)

是不是有点小激动,又回到写证明题的时候了!😁

根据程序顺序规则可以得出: A happen-before B , C happen-before D
根据volatile变量规则可以得出: B happen-before C
再根据传递性规则可以得出: A happen-before C 和 A happen-before D
证毕。

是不是发现很简单…

2.5 线程启动规则

定义:在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。

 int a = 1;                  --------A
 new Thread(() -> {
     System.out.println(a);  --------C         
 }).start();                 --------B

根据线程启动规则可以得出:A happen-before C。这说明什么?说明父子线程比其他独立的两个线程更特殊,两个独立线程执行过程中需要通过其他规则(比如:volatile变量规则)保证可见性,而父线程在所有在子线程启动之前的操作对应子线程都可见。

2.6 线程终止规则

定义:在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。
参考线程启动规则,不做赘述。

2.7 线程中断规则

定义:对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生。
说明:如果没有这个规则的话,线程被中断之后,Thread.interrupted()无法检查到中断发生,外部依赖的程序就无法正常执行。

2.8 对象终结规则

定义:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。
说明:这个比较简单,如果先执行finalize(),初始化的后半部分就会出现问题,因为找不到该对象了。

关注我的公众号,不迷路

公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值