1、概述
在JMM规范下,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happers-before(先行发生)原则。
例如
int x = 10 ;
int y = x;
这两行代码中第二个操作 y=x ,因为需要将x值赋值给y,所以第一行代码的结果需要对第二行代码可见。也就是第一行代码happers-before第二行代码。
有些朋友看了可能觉得,作者说的不是废话嘛,这两行代码肯定是先执行第一行再执行第二行。答案肯定是的,因为y的值依赖x的值,这属于JMM规范happens-before原则内的,所以会按照代码编写顺序执行。假如没有happens-before原则,则y的值可能就不会等于10,可见happens-before原则的重要性。
happens-before原则是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个原则,我们可以通过几条简单的规则,解决并发环境下两个操作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型苦涩难懂的底层编译原理之中。
那接下来我们就说说happens-before都有那些具体原则。
2、happens-before具体原则
- 次序规则
一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作。
注意:这里是一个线程内,如果是多个线程这不属于happens-before原则
int x = 10;
int y = 20;
在一个线程内 在执行y=20 时,x肯定是10;但是如果在多线程下由于编译器优化,指令重排序 在执行y=20时,x可能等于0
- 锁定规则
一个unlock操作先行发生于后面(这里的”后面“是指时间上的先后)对同一把锁的lock操作;
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
System.out.println("------锁定1");
} finally {
//解锁
lock.unlock();
}
//再次锁定 第二次加锁操作肯定是发生在上一次解锁 之后
lock.lock();
try {
System.out.println("------锁定2");
} finally {
lock.unlock();
}
}
- volatile变量规则
对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的”后面“也是指时间上的先后。
咱们都知道被volatile修饰的变量,拥有JMM三大特性的可见性和有序性。对于同一个共享volatile变量,线程A修改了变量值,则其他线程立马就可以看到。
static volatile Boolean flag = false;
public static void main(String[] args) {
new Thread(() -> {
if (!flag) {
System.out.println("--正常运行");
}
System.out.println("--终止");
}, "t1").start();
new Thread(() -> {
//更改volatile 变量值,t1线程立马就能收到
flag = true;
System.out.println("终止线程t1");
}, "t2").start();
}
看到这里是不是就知道了,为啥 volatile 修饰变量,变量具有可见性和有序性,其实底层是happens-before原则制定的。
- 传递性规则
如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A肯定先行发生于C。
- 线程启动规则
Thread对象的start()方法先行发生于此线程中执行的所有代码。
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
/**
* 线程内操作 后执行
*/
System.out.println("线程执行内容1");
System.out.println("线程执行内容2");
}, "t1");
// start()方法先执行
t1.start();
}
- 线程中断规则
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()或者isInterrupted()检测到是否有中断发生。
Thread t1 = new Thread(() -> {
//检查线程中断标识位是否被设置为true
while (!Thread.currentThread().isInterrupted()) {
System.out.println("---正常执行");
}
System.out.println("被中断");
}, "t1");
//线程1启动
t1.start();
new Thread(() -> {
System.out.println("将t1 线程中断标识位设置为true");
t1.interrupt();
}, "t2").start();
—正常执行
…..
…..
—正常执行
—正常执行
—正常执行
—正常执行
将t1 线程中断标识位设置为true
—正常执行
被中断
- 线程终止规则
线程中的所有操作都先行发生于对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
- 对象终结规则
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
这条规则指出,一个对象在被垃圾回收之前必须已经进过初始化,垃圾回收不可能也不能去回收一个根本不存在的对象。
3、总结
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序是可以正常执行的。
暂无评论内容