这个我想是大家学习 Java 并发编程中非常容易忽略的一个点,为什么,因为太抽象了

我刚开始学习的时候遇到 happens-before 的时候也是不明觉厉,"哪来的这么一个破玩意"!

happens - before 不像是什么 Java 并发工具类能够浅显易懂,容易上手。happens - before 重在理解。

happens - before 和 JMM 也就是 Java 内存模型有关,所以我们需要先从 JMM 入手,才能更好的理解 happens - before 原则。

JMM 的设计

JMM 是 JVM 的基础,因为 JVM 中的堆区、方法区、栈区都是建立在 JMM 基础上的,你可能还是不理解这是怎么回事,没关系,我们先来看一下 JMM 的模型。

JVM 的划分想必大家应该了然于胸,这里就不再赘述了,我们主要说一下 JVM 各个区域在 JMM 中的分布。JVM 中的栈区包括局部变量和操作数栈,局部变量在各个线程之间都是独立存在的,即各个线程之间不会互相干扰,变量的值只会受到当前线程的影响,这在《Java 并发编程实战》中被称为线程封闭

然而,线程之间的共享变量却存储在主内存(Main Memory)中,共享变量是 JVM 堆区的重要组成部分。

那么,共享变量是如何被影响的呢?

这里其实有操作系统层面解决进程通信的一种方式:共享内存,主内存其实就是共享内存。

之所以说共享变量能够被影响,是由于每个 Java 线程在执行代码的过程中,都会把主内存中的共享变量 load 一份副本到工作内存中。

当每个 Java 线程修改工作内存中的共享变量副本后,会再把共享变量 store 到主存中,由于不同线程对共享变量的修改不一样,而且每个线程对共享变量的修改彼此不可见,所以最后覆盖内存中共享变量的值的时候可能会出现重复覆盖的现象,这也是共享变量不安全的因素。

由于 JMM 的这种设计,导致出现了我们经常说的可见性有序性问题。

关于可见性和 Java 并发编程中如何解决可见性问题,我们在 volatile 这篇文章中已经详细介绍过了。实际上,在 volatile 解决可见性问题的同时,也是遵循了 happens - before 原则的。

happens - before 原则

JSR-133 使用 happens - before 原则来指定两个操作之间的执行顺序。这两个操作可以在同一个线程内,也可以在不同线程之间。同一个线程内是可以使用 as-if-serial 语义来保证可见性的,所以 happens - before 原则更多的是用来解决不同线程之间的可见性。

JSR - 133 对 happens - before 关系有下面这几条定义,我们分别来解释下。

程序顺序规则

Each action in a thread happens-before every subsequent action in that thread.

每个线程在执行指令的过程中都相当于是一条顺序执行流程:取指令,执行,指向下一条指令,取指令,执行。

而程序顺序规则说的就是在同一个顺序执行流中,会按照程序代码的编写顺序执行代码,编写在前面的代码操作要 happens - before 编写在后面的代码操作。

这里需要特别注意⚠️的一点就是:这些操作的顺序都是对于同一个线程来说的。

monitor 规则

An unlock on a monitor happens-before every subsequent lock on that monitor.

这是一条对 monitor 监视器的规则,主要是面向 lock 和 unlock 也就是加锁和解锁来说明的。这条规则是对于同一个 monitor 来说,这个 monitor 的解锁(unlock)要 happens - before 后面对这个监视器的加锁(lock)。

比如下面这段代码

class monitorLock {private int value = 0;public synchronized int getValue() {return value;}public synchronized void setValue(int value) {this.value = value;}
}

在这段代码中,getValue 和 setValue 这两个方法使用了同一个 monitor 锁,假设 A 线程正在执行 getValue 方法,B 线程正在执行 setValue 方法。monitor 的原则会规定线程 B 对 value 值的修改,能够直接对线程 A 可见。如果 getValue 和 setValue 没有 synchronized 关键字进行修饰的话,则不能保证线程 B 对 value 值的修改,能够对线程 A 可见。

monitor 的规则对于 synchronized 语义和 ReentrantLock 中的 lock 和 unlock 的语义是一样的。

volatile 规则

A write to a volatile field happens-before every subsequent read of that volatile.

这是一条对 volatile 的规则,它说的是对一个 volatile 变量的写操作 happens - before 后续任意对这个变量的读操作。

嗯,这条规则其实就是在说 volatile 语义的规则,因为对 volatile 的写和读之间会增加 memory barrier ,也就是内存屏障。

内存屏障也叫做栅栏,它是一种底层原语。它使得 CPU 或编译器在对内存进行操作的时候, 要严格按照一定的顺序来执行, 也就是说在 memory barrier 之前的指令和 memory barrier 之后的指令不会由于系统优化等原因而导致乱序。

线程 start 规则

A call to start() on a thread happens-before any actions in the started thread.

这条规则也是适用于同一个线程,对于相同线程来说,调用线程 start 方法之前的操作都 happens - before start 方法之后的任意操作。

这条原则也可以这样去理解:调用 start 方法时,会将 start 方法之前所有操作的结果同步到主内存中,新线程创建好后,需要从主内存获取数据。这样在 start 方法调用之前的所有操作结果对于新创建的线程都是可见的。

我来画幅图给你看。

可以看到,线程 A 在执行 ThreadB.start 方法之前会对共享变量进行修改,修改之后的共享变量会直接刷新到内存中,然后线程 A 执行 ThreadB.start 方法,紧接着线程 B 会从内存中读取共享变量。

线程 join 规则

All actions in a thread happen-before any other thread successfully returns from a join() on that thread.

这条规则是对多条线程来说的:如果线程 A 执行操作 ThreadB.join() 并成功返回,那么线程 B 中的任意操作都 happens - before 于线程 A 从 ThreadB.join 操作成功返回。

假设有两个线程 s、t,在线程 s 中调用 t.join() 方法。则线程 s 会被挂起,等待 t 线程运行结束才能恢复执行。当t.join() 成功返回时,s 线程就知道 t 线程已经结束了。所以根据本条原则,在 t 线程中对共享变量的修改,对 s 线程都是可见的。类似的还有 Thread.isAlive 方法也可以检测到一个线程是否结束。

线程传递规则

If an action a happens-before an action b, and b happens before an action c, then a happensbefore c.

这是 happens - before 的最后一个规则,它主要说的是操作之间的传递性,也就是说,如果 A happens-before B,且 B happens-before C,那么 A happens-before C。

线程传递规则不像上面其他规则有单独的用法,它主要是和 volatile 规则、start 规则和 join 规则一起使用。

和 volatile 规则一起使用

比如现在有四个操作:普通写、volatile 写、volatile 读、普通读,线程 A 执行普通写和 volatile 写,线程B 执行volatile 读和普通读,根据程序的顺序性可知,普通写 happens - before volatile 写,volatile 读 happens - before 普通读,根据 volatile 规则可知,线程的 volatile 写 happens - before volatile 读和普通读,然后根据线程传递规则可知,普通写也 happens - before 普通读。

和 start() 规则一起使用

和 start 规则一起使用,其实我们在上面描述 start 规则的时候已经描述了,只不过上面那幅图少画了一条线,也就是 ThreadB.start happens - before 线程 B 读共享变量,由于 ThreadB.start 要 happens - before 线程 B 开始执行,然而从程序定义的顺序来说,线程 B 的执行 happens - before 线程 B 读共享变量,所以根据线程传递规则来说,线程 A 修改共享变量 happens - before 线程 B 读共享变量,如下图所示。

和 join() 规则一起使用

假设线程 A 在执行的过程中,通过执行 ThreadB.join 来等待线程 B 终止。同时,假设线程 B 在终止之前修改了一些共享变量,线程 A 从 ThreadB.join 返回后会读这些共享变量。

在上图中,2 happens - before 4 由 join 规则来产生,4 happens - before 5 是程序顺序规则,所以根据线程传递规则,将会有 2 happens - before 5,这也意味着,线程 A 执行操作 ThreadB.join 并成功返回后,线程 B 中的任意操作将对线程 A 可见。

热门内容:当 Docker 遇到 Intellij IDEA,再次解放了生产力~你真的会写for循环吗?来看看这些常见的for循环优化方式居然有人提问“国家何时整治程序员的高薪现象”?Spring官宣新家族成员:Spring Authorization Server!最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

我真不想学 happens - before 了!相关推荐

  1. 汕头大学计算机专业荣誉,广西考生进入汕头大学要超一本线三四十分,但我想学的计算机专业在全国排名很低,真不知汕大的这专业有啥...

    技校网专门为您推荐的类似问题答案 问题1: 艺术类院校 天津工业大学和汕头大学,哪个专业好点,或者什么综合好点,都是一本的 汕大录取概率大多了,很有希望,一定我就不干打包票.但是我得告诉你 汕头相对天 ...

  2. 想学 Java 的你,来看看这 20 个实战项目!

    在<程序员修炼之道>一书中,作者建议程序员每年至少学习一门新语言,每季度阅读一本技术书籍.我认为非常有道理.学习编程语言主要是语法.库和框架这三个内容,掌握了正确的方法一个月就可以开始写 ...

  3. 曾经想学很多很多,最后发现自己只能专心学那么很少的几个必杀技

    为什么80%的码农都做不了架构师?>>>    很多人觉得,我特能乱写,其实参加工作都10年了,每个月就算发生一件事情一个感慨,也足够可以写120篇了.今天不知道怎么回事,彻底失眠了 ...

  4. 我在互联网上买了很多课程,但是感觉在互联网学习没有什么效果很多买的课我都不想学了

    最近有一个朋友问我一个问题:我在互联网上买了很多课程,但是感觉在互联网学习没有什么效果很多买的课我都不想学了. 我说,这个很正常,就像我们日常培训一样,纯面授式的只能吸收10%的内容. 而且纯靠互联网 ...

  5. 不会英语学习c语言和java,我想学电脑JAVA,但不懂英文,可以学吗?怎么才能学好呢...

    我想学电脑JAVA,但不懂英文,可以学吗?怎么才能学好呢以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 我想学电脑JAV ...

  6. 给自学者的建议:想学游戏编程开发,你需要做什么?

    声明:只是个人见解!!! PS 2013/03/11 17:02 Yellow Lee早上在百度游戏开发贴吧,看到里面好多人很迷惘,加入置 顶的群,聊了一下,发现迷惘的人更多,做点事吧,憋着一口气写了 ...

  7. 我想学单片机,但不知从何下手

    2006-05-02 20:43:18 ──┼阅览┼── 求教 --------- 发表时间 2001-12-31 11:51:23 - 来自 61.144.182.82 - love 我想学单片机, ...

  8. 合肥计算机函授专业,2015年想学电脑,合肥哪个学校比较好点,中国计算机函授学院怎么样?...

    技校网专门为您推荐的类似问题答案 问题1: 2015年合肥哪个计算机学校比较好? 其实哪个学校不在于好坏 再好的学校也有差的学生,再坏的学校也有优秀的学生, 学习在于自己 推荐的学校如下:合肥新华电脑 ...

  9. 想学ui设计从哪里入手?基础怎么入门学习UI设计呢?

    对于零基础的小伙伴们来说,学习UI设计的头绪是比较大的,虽然有很多的书籍和视频可以供参考,但是很多人缺乏规律性,缺乏自学精神.因此零基础学习UI设计就相当吃力了.怎么样学习决定了你将来学完后的成果.优 ...

最新文章

  1. Python的系统管理_12_rrdtool
  2. trackingmore快递查询平台_快递物流服务再升级!寄快递更便捷,看看都有哪些平台...
  3. python进行数据分析 简书_《利用python进行数据分析》读书笔记1
  4. 暑假第二周总结(2018.7.16——7.22)
  5. 20191208_神经网络搭建_缺失值箱型图
  6. oracle 行列转换 pivot unpivot (本文来自官网)
  7. Linux与git库建立连接,Linux 下建立 Git 与 GitHub 的连接
  8. 教学质量分析系统 php,教学质量分析系统演示.ppt
  9. 思科网技术学院教程(第三、四学期第二版)学习笔记与要点归纳
  10. 将数据与OpenLayers结合在一起
  11. 谈谈在ISA防火墙中的端口映射方法
  12. peoplesoft链接oracle,Oracle 调整对PeopleSoft HRMS8.8的支持延伸到2011年
  13. 2D动画设计制作软件:Cartoon Animator 中文版win/mac版
  14. 计算机网络多项式的定义,多项式
  15. Scapy:查看sniff函数抓取的包
  16. 基于ubuntu 20.04与cri-docker 搭建部署高可用k8s 1.25.3
  17. 安装到部署 火绒安全企业新品究竟有多简?
  18. 机械式键盘软件测试,驱动软件测试 中/低端篇_新贵 GM-500机械键盘_键鼠评测-中关村在线...
  19. 夏驰和徐策的解决数学问题思路——反证法
  20. 英语计算机单词mp3,【听单词】计算机专业英语词汇音频106,计算机英语单词MP3...

热门文章

  1. php imagecolorallocate 安装,PHP imagecolorallocate()和imagecolorallocatealpha():定义颜色
  2. 用chrome的snippets片段功能创建页面js外挂程序,从控制台创建js小脚本
  3. FFmpeg 与媒体文件关系
  4. git查看某个文件的提交历史
  5. PHP PSR-4 Autoloader 自动加载(中文版)
  6. test markdown
  7. 记录-MySQL中的事件调度Event Scheduler
  8. Intel 6系列芯片组设计缺陷 全球出货暂停
  9. 【NCEPU】吴丹飞:(CSAPP)计算机系统漫游
  10. 22个案例详解 Pandas 数据分析/预处理时的实用技巧,超简单