老爷子这代码,看跪了! (中)

简介: 老爷子这代码,看跪了! (中)

基础铺垫


为了文章的顺利进行,必须得进行一个基础知识的铺垫,那就是 Happens-Before 关系。

而 Happens-Before 关系的正式提出,就是 jsr 133 规范:

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf


image.png

如果你不知道 jsr133 是啥,那么可以去这个链接里面看看。

http://ifeve.com/jsr133/

在这里面就有大家耳熟能详的 Happens-Before 关系的正式描述,大家看到的所有的中文版翻译的原文,就是这里:


image.png

由于这段话,特别是那六个小黑点后面的话太重要了,失之毫厘谬以千里,所以我不敢轻易按照之前的轻松风格大致翻译。

于是我决定站在大佬的肩膀上,分别把《深入理解Java虚拟机(第三版)》、《Java并发编程实战》、《Java并发编程的艺术》这三本书中关于这部分的定义和描述搬运一下,大家对比着看。

如果对于该规则了然于心,可以跳过本小节。

网络异常,图片无法展示
|

走起。

首先是《深入理解Java虚拟机(第三版)》:

  • 程序次序规则(Program Order Rule):在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。注意,这里说的是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
  • 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的 lock操作。这里必须强调的是“同一个锁”,而“后面”是指时间上的先后。
  • volatile 变量规则(Volatile Variable Rule):对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后。
  • 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行。
  • 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread:interrupted()方法检测到是否有中断发生。
  • 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
  • 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

接着是《Java并发编程实战》:

  • 程序顺序规则:如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行。
  • 监视器锁规则:在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。
  • volatile 变量规则:对volatile 变量的写入操作必须在对该变量的读操作之前执行。
  • 线程启动规则:在线程上对Thread.Start的调用必须在该线程中执行任何操作之前执行。
  • 线程结束规则:线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回 false.
  • 中断规则:当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterruptedException,或者调用isInterrupted和interrupted).
  • 终结器规则:对象的构造函数必须在启动该对象的终结器之前执行完成。
  • 传递性:如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A必须在操作C之前执行。

《Java并发编程的艺术》,在这本书里面作者加了一个限定词“与程序员密切相关的 happens-before规则如下”:

  • 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
  • volatile 变量规则:对一个volatile域的写,happens-before 于任意后续对这个 volatile 域的读。
  • 传递性:如果 A happens-before B,且B happens-before C,那么 A happens-before C。

也就是说:线程启动规则、线程结束规则、中断规则、对象终结规则其实对于开发来说是无感的,在这几个规则里面,我们没有什么可以搞事的空间。

当你把这三本书中对于同一件事情的描述对比着来看的时候,也许会稍微的印象深刻一点吧。

本质上说的是一回事,只是描述略有不同而已。

另外,我觉得我需要补充一个我觉得非常重要的点,那就是在原文论文中多处出现的一个非常重要的单词 action:


image.png

那么啥是 action?

对于这个略显模糊的定义,论文开篇的第五点提到了具体含义:

In this section we define in more detail some of the informal concepts we have presented.

在本节中,我们将更详细地定义一些我们提出的非正式概念。

其中对论文中的七个概念进行了详细描述,分别是:

  • Shared variables/Heap memory
  • Inter-thread Actions
  • Program Order
  • Intra-thread semantics
  • Synchronization Actions
  • Synchronization Order
  • Happens-Before and Synchronizes-With Edges

其中,我个人理解,happens-before 中的 action 主要是说下面这个三个概念:

image.png

线程间(inter-thread)动作、线程内(intra-thread)动作、同步动作(Synchronization Actions)。

加锁、解锁、对 volatile 变量的读写、启动一个线程以及检测线程是否结束这样的操作,均为同步动作。

而线程间(inter-thread)动作与线程内(intra-thread)动作是相对而言的。比如一个线程对于本地变量的读写,也就是栈上分配的变量的读写,是其他线程无法感知的,这是线程内动作。而线程间动作比如对于全局变量的读写,也就是堆里面分配的变量,其他线程是可以感知的。

另外,你看 Inter-thread Actions 里面我画下划线的地方,描述其实和同步动作相差无几。我理解,其实线程间动作大多也就是同步动作。

所以你去看一本书,叫做《深入理解Java虚拟机HotSpot》,这本书里面对于happens-before 的描述就稍微有点不一样了,开篇加的限定条件就是“所有同步动作...”:

1)所有同步动作(加锁、解锁、读写volatile变量、线程启动、线程完成)的代码顺序与执行顺序一致,同步动作的代码顺序也叫作同步顺序。

1.1)同步动作中对于同一个monitor,解锁发生在加锁前面。

1.2)同一个volatile变量写操作发生在读操作前面。

1.3)线程启动操作是该线程的第一个操作,不能有先于它的操作发生。

1.4)当T2线程发现T1线程已经完成或者连接到T1,T1的最后一个操作要先于T2 所有操作。

1.5)如果线程T1中断线程T2,那么T1中断点要先于任何确定T2被中断的线程的 操作。

对变量写入默认值的操作要先于线程的第一个操作;对象初始化完成操作要先 于finalize()方法的第一个操作。

2)如果a先于b发生,b先于c发生,那么可以确定a先于c发生。

3)volatile的写操作先于volatile的读操作。

本来,我还想举出《Java编程思想》里面关于 happens-before 的描述的。

结果,我翻完了书中关于并发的部分,结果它:

没,有,写!

好吧,我想有可能这本神书写于 2004 年 jsr133 发布之前?

结果,它的英文版发布时间是在 2006 年,也就是作者故意没写的,他只是在 21.11.1 章节里面提到了《Java Concurreny in Practice》:

image.png

目录
相关文章
|
3月前
|
Web App开发 自然语言处理
一盏茶的功夫带你掌握烦人的 this 指向问题( 一 )
一盏茶的功夫带你掌握烦人的 this 指向问题( 一 )
|
3月前
|
Web App开发 自然语言处理
一盏茶的功夫带你掌握烦人的 this 指向问题( 二 )
一盏茶的功夫带你掌握烦人的 this 指向问题( 二 )
|
Go
腥风血雨中,这招救了我的代码!
腥风血雨中,这招救了我的代码!
55 0
|
存储 安全 Python
python多线程------>这个玩意很哇塞,你不来看看吗
python多线程------>这个玩意很哇塞,你不来看看吗
|
前端开发 程序员 开发工具
你疯了吧,竟然在代码里面“下毒”?
除了有点味道以外,这回是不记住了,我们编程写代码的过程和我们日常生活的例子,往往都是这样可以对应上,有了真实可以触及的实物,再去了解编程就会更加容易,也很难忘记。但可能会写着写着代码,就傻笑起来!
|
Java 关系型数据库 MySQL
【浅尝高并发编程】接私活差点翻车
作为一名本本分分的练习时长两年半的Java练习生,一直深耕在业务逻辑里,对并发编程的了解仅仅停留在八股文里。一次偶然的机会,接到一个私活,核心逻辑是写一个 定时访问api把数据持久化到数据库的小服务。
174 0
|
前端开发 JavaScript IDE
YourBatman用趣味代码雨祝你:端午安康
使用Java的AWT给你写了个祝福
235 0
YourBatman用趣味代码雨祝你:端午安康
|
Java
老爷子这代码,看跪了! (上)
老爷子这代码,看跪了! (上)
147 0
老爷子这代码,看跪了! (上)
|
安全 Java
老爷子这代码,看跪了! (下)
老爷子这代码,看跪了! (下)
130 0
老爷子这代码,看跪了! (下)
|
设计模式 移动开发 安全
与其硬啃“屎山”代码,不如用这六步有条不紊实现代码重构 李慧文
对大规模系统进行重构,如果一个人对着又臭又长的代码硬刚,即使花了大量的时间进行手工验证,最后仍然会有很多问题,特别是一些深路径及特殊场景下的问题。其实,大规模的系统级别重构时是有方法的。我们采访了 Thoughtworks 数字化转型与运营 资深咨询师黄俊彬(QCon+案例研习社讲师),请他来分享 MV*模式重构演进的方法和经验。
569 0
与其硬啃“屎山”代码,不如用这六步有条不紊实现代码重构 李慧文

相关实验场景

更多