面试题:happen-before的八个基本规则你知道吗?

Happens-Before

Happens-Before真正要表达的是:前面一个操作的结果对后续操作是可见的

就像有心灵感应的两个人,虽然远隔千里,一个人心之所想,另一个人都看得到。Happens-Before 规则就是要保证线程之间的这种“心灵感应”。

Happens-Before原则它是判断数据是否存在竞争线程是否安全的主要依据,依靠这个原则,我们解决在并发环境下两操作之间是否可能存在冲突的所有问题

所以比较正式的说法是:Happens-Before约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守 Happens- Before 规则。

在java内存模型(JMM)中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。

八个基本规则

Happens-Before程序员相关的规则一共有如下八项,都是关于可见性的。

1. 程序的顺序性规则

这条规则是指在一个线程中,按照程序顺序,前面的操作 Happens-Before于后续的任意操作

比如下面的示例代码,按照程序的顺序,“x = 42;” Happens-Before 于代码 v = x;”,这就是规则1的内容,也比较符合单线程里面的思维:程序前面对某个变量的修改一定是对后续操作可见的。

class VolatileExample { int x = 0; volatile int v = 10; public void writer() { x = 42;v = x; } } }

2. volatile 变量规则

这条规则是指对一个volatile变量的写操作,Happens-Before 于后续对这个volatile变量的读操作

这个就有点费解了,对一个volatile变量的写操作相对于后续对这个 volatile 变量的读操作可见,这怎么看都是禁用缓存的意思啊,貌似和 1.5 版本以前的语义没有变化啊?如果
单看这个规则,的确是这样,但是如果我们关联一下规则 3,就有点不一样的感觉了。

3. 传递性

这条规则是指如果 A Happens-Before B,且 B Happens-Before C,那么A Happens-Before C。

下面我们结合具体的代码,我们利用这条规则推导下

public class VolatileExample {private int a = 0;private volatile boolean flag = false;public void writer(){a = 1;          //1flag = true;   //2}public void reader(){if(flag){      //3int i = a; //4}}
}

我们假设这里有线程A和线程B,线程A先执行writer方法,然后再线程B执行reader方法。我们将规则3的传递性和规则2和规则1应用到我们的例子中,会发生什么呢?可以看下面这幅图:

从图中,我们可以看到:

  1. 根据程序顺序规则推导出来黑色的线条
  2. 根据volatile变量规则推导出来红色的线条,volatile写happens-before 于任意后续对volatile变量的读,也就是说当线程A将volatile变量 flag更改为true后线程B就能够迅速感知,是可见的
  3. 根据传递性规则推导出来蓝色的线条,我们得到结果:“a=1” Happens-Before读变量“ int i = a;”,这就是 1.5 版本对volatile 语义的增强,这个增强意义重大,1.5版本的并发工具包(java.util.concurrent)就是靠volatile语义来搞定可见性的,这个在后面的内容中会详细介绍

分析完前三个happens-before关系后我们现在就来进一步分析volatile的内存语义,还是以上面的代码为例,假设线程A先执行writer方法,线程B随后执行reader方法,初始时线程的本地内存中flag和a都是初始状态,下图是线程A执行volatile写后的状态图。

当volatile变量写后,线程中本地内存中共享变量就会置为失效的状态,因此线程B再需要读取从主内存中去读取该变量的最新值。下图就展示了线程B读取同一个volatile变量的内存变化示意图

从横向来看,线程A和线程B之间进行了一次通信,线程A在写volatile变量时,实际上就像是给B发送了一个消息告诉线程B你现在的值都是旧的了,然后线程B读这个volatile变量时就像是接收了线程A刚刚发送的消息。既然是旧的了,那线程B自然而然就只能去主内存去取啦

4. 管程中锁的规则

这条规则是指对一个锁的解锁Happens-Before于后续对这个锁的加锁

要理解这个规则,就首先要了解管程指的是什么。管程是一种通用的同步原语,在Java中指的就是 synchronized,synchronized是Java里对管程的实现。管程中的锁在Java里是隐式实现的.

例如下面的代码,在进入同步块之前,会自动加锁,而在代码块执行完会自动释放锁,加锁以及释放锁都是编译器帮我们实现的。当然Lock中也是一样,一个unLock操作先行发生于后面对同一个锁的lock操作

 synchronized (this) { // 此处自动加锁 // x 是共享变量, 初始值 =10 if (this.x < 12) { this.x = 12; } } // 此处自动解锁

所以结合规则 4管程中锁的规则,可以这样理解:假设 x 的初始值是 10,线程 A 执行完代码块后 x的值会变成 12(执行完自动释放锁),线程B进入代码块时,能够看到线程 A 对 x的写操作,也就是线程B能够看到x==12。这个也是符合我们直觉的,应该不难理解。

5. 线程 start() 规则

这条是关于线程启动的。它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程B前的操作

换句话说就是,如果线程A调用线程B的start()方法(即在线程 A 中启动线程B),那么该start()操作Happens-Before 于线程 B中的任意操作。具体可参考下面示例代码

Thread B = new Thread(()->{ // 主线程调用 B.start() 之前 // 所有对共享变量的修改,此处皆可见 // 此例中,var==77
});
// 此处对共享变量 var 修改
var = 77;
// 主线程启动子线程
B.start();

6. 线程中断规则

线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生

7. 线程终结规则

这条是关于线程等待的,线程中所有的操作都先行发生于线程的终止检测.

我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。

例如它是指主线程A等待子线程B完成(主线程A通过调用子线程B的join()方法实现),当子线程B完成后(主线程A中join()方法返回),主线程能够看到子线程的操作。当然所谓的“看到”,指的是对共享变量的操作。换句话说就是,如果在线程A中,调用线程B的join()并成功返回,那么线程B中的任意操作Happens-Before于该join()操作的返回。具体可参考下面示例代码。


Thread B = new Thread(()->{ // 此处对共享变量 var 修改 var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程 B 可见
// 主线程启动子线程 8 B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用 B.join() 之后皆可见
// 此例中,var==66

8. 对象终结规则

一个对象的初始化完成先行发生于他的finalize()方法的开始

其他规则

上面八条是原生Java满足Happens-before关系的规则,但是我们可以对他们进行推导出其他满足happens-before的规则:

  1. 将一个元素放入一个线程安全的队列的操作Happens-Before从队列中取出这个元素的操作
  2. 将一个元素放入一个线程安全容器的操作Happens-Before从容器中取出这个元素的操作
  3. 在CountDownLatch上的倒数操作Happens-Before CountDownLatch#await()操作
  4. 释放Semaphore许可的操作Happens-Before获得许可操作
  5. Future表示的任务的所有操作Happens-Before Future#get()操作
  6. 向Executor提交一个Runnable或Callable的操作Happens-Before任务开始执行操作

这里再说一遍happens-before的概念:

如果两个操作不存在上述(前面8条+后面6条)任一一个happens-before规则,那么这两个操作就没有顺序的保障,JVM可以对这两个操作进行重排序。如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的。

小结

happen-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。

如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。 感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。

原创不易,欢迎转发,关注公众号“码农进阶之路”,获取更多面试题,源码解读资料!

《菜鸟读并发》java内存模型之happen-before相关推荐

  1. 多线程并发-java内存模型和计算机基础

    CPU缓存一致性协议MESI CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU.这就造成了高性能能的内存和硬盘价格及其昂贵.然而CPU的高度运算需要高速的 ...

  2. 一文读懂Java内存模型(JMM)及volatile关键字

    点赞再看,养成习惯,公众号搜一搜[一角钱技术]关注更多原创技术文章. 本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章. 前言 并发编程从操作系统底层工作的整 ...

  3. 深入探索JVM高效并发 — Java内存模型(四) 先行发生原则

    先行发生原则 Java语言中有一个"先行发生"(Happens-Before)的原则.这个原则非常重要,它是判断数据是否存在竞争,线程是否安全的非常有用的手段. 依赖这个原则,我们 ...

  4. blp模型 上读下写_Java高并发编程(三):Java内存模型

    1 Java内存模型的基础 在并发编程里,需要处理两个问题: 线程之间如何通信 线程之间如何同步. 通信指的是线程之间以何种机制来交换信息.在命令式编程里中,线程之间的通信机制有两种:共享内存和消息传 ...

  5. JSR 133 Java内存模型以及并发编程的最权威论文汇总

    Java内存模型 先看官方文档: https://docs.oracle.com/javase/specs/ JSR 133:Java TM内存模型和线程规范修订版:https://www.jcp.o ...

  6. 聊聊高并发(三十六)Java内存模型那些事(四)理解Happens-before规则

    在前几篇将Java内存模型的那些事基本上把这个域底层的概念都解释清楚了,聊聊高并发(三十五)Java内存模型那些事(三)理解内存屏障 这篇分析了在X86平台下,volatile,synchronize ...

  7. 【Java并发编程】之十六:深入Java内存模型——happen-before规则及其对DCL的分析(含代码)...

    Java并发编程系列 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/mmc_maodun/article/details/17348313 转载请注 ...

  8. 并发编程专题——第一章(深入理解java内存模型)

    说到并发编程,其实有时候觉得,开发中真遇到这些所谓的并发编程,场景多吗,这应该是很多互联网的在职人员,一直在考虑的事情,也一直很想问,但是又不敢问,想学习的同时,网上这些讲的又是乱七八糟,那么本章开始 ...

  9. Java并发编程:Java内存模型JMM

    简介 Java内存模型英文叫做(Java Memory Model),简称为JMM.Java虚拟机规范试图定义一种Java内存模型来屏蔽掉各种硬件和系统的内存访问差异,实现平台无关性. CPU和缓存一 ...

最新文章

  1. php数据库滚动文字_PHP 里用的文字左右滚动?
  2. Stefan Tilkov:跳过单体应用,从微服务开始
  3. 论文阅读: Direct Monocular Odometry Using Points and Lines
  4. 细节之中自有天地,整洁成就卓越代码
  5. C雨涵课后习题(18)
  6. java多个条件排序_java定制化排序,多个条件排序
  7. Activiti 流程实例、任务、执行对象及相关的表
  8. 使用 sroll-snap-type 优化滚动
  9. latex longtable and supertabular 跨页表格
  10. 融合迁移学习与文本增强的中文成语隐喻知识识别与关联研究
  11. Python 快速设置 Excel 表格边框
  12. 计算机搜索功能在分区里失灵,DiskGenius搜索已丢失分区(重建分区表)
  13. Java开发WIN10动态壁纸
  14. colunm-count, orphans,widows
  15. 计算机存储单位t代表什么意思,存储单位是什么
  16. BZOJ 1106: [POI2007]立方体大作战tet
  17. 32 Qt 之绘图之绘制一个漂亮的西瓜
  18. 【好题分享】适合C++初学者(数组的定义与初始化)
  19. 2020年11月4日
  20. 基于java+springboot+mybatis+vue+elementui的古玩玉器交易系统

热门文章

  1. 工业级宽温版RK3399K核心板发布
  2. 关于七牛云存储问题汇总(持续更新)
  3. Hibernate项目搭建所需Jar包合集 5.0.7版本 免费下载【微云网盘】
  4. 十,iOS 健康数据获取权限和写入权限
  5. ajax脚本下载_E4X的AJAX和脚本Web服务,第1部分
  6. 设计模式之----状态模式(State-pattern)的理解
  7. 【腾讯Bugly干货分享】人人都可以做深度学习应用:入门篇
  8. 方形图片 圆形图片 各种形状
  9. 二重积分极坐标展开与三重积分先“2后1”中的“2”极坐标展开区别
  10. 解决object references an unsaved transient instance - save the transient instance before flushing 的错误