面试官:讲讲happens-before
happens-before是判断数据是否存在竞争、线程是否安全的重要依据
JMM的自我介绍
Hello 大家好,我原名叫Java Memory Model(Java 内存模型),大家都叫我JMM,简洁又好听,我挺喜欢。
并发编程这块,没有我可是不行的,我要解决的问题就是一个线程对共享变量的写入何时对另一个线程可见
比如一个线程给 变量 a 赋值 a = 3; // 往变量 a 写值
我要解决的问题就是:“在什么条件下,读取变量 a 的线程将看到这个值 3”
如果缺少同步,那会有很多因素使得 读取变量 a 的线程不能立即看到或者永远看不到这个值 3
在我的世界里,所有的变量都存储在主内存中,每一个线程都有一个私有的本地内存,本地内存中存储了该线程使用到的变量在主内存中拷贝
线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量也不例外),如下图
好了,介绍了这么多,你就知道有我这么个人就行
JMM发布新公告
“最近出大事了”,程序员小张说道。“哦,什么大事?”小李问道
“JMM那家伙发布了一个公告,说他弄了个什么 happens-before规则”小张回应道,“哦,那咱们去看看”小李说。
他们来到公告前,看到公告上写着:
程序员们,你们的福利来了
如果你们在编程的时候,想要一个操作执行的结果需要对另一个操作可见,那么你们可以使用 happens-before 规则
那什么是 happens-before规则呢?
举个例子:
i = 1; // 操作 A
j = i; // 操作 B如果 操作A happens-before 于 操作B,那么就可以确定,操作B执行完之后,j 的值一定为 1;因为happens-before关系可以向程序员保证:在操作B执行之前,操作A的执行后的影响[或者说结果](修改 i 的值)操作B是可以观察到的[或者说可见的]
换句话说,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,在这个例子就是A操作的结果要对B操作可见,那么必然存在A happens-before B
简而言之:使用happens-before的概念来阐述操作之间的内存可见性
那在编程中有哪些情况符合这个happens-before规则呢?我怎么使用
这里列举几个常见的Java“天然的”happens-before关系,回头给你们发个全面的手册① 程序顺序规则: 一个线程中的每个操作,happens-before于该线程中的任意后续操作(也就是说你写的操作,如果是单线程执行,那么前面的操作[程序逻辑上的前]就会happens-before于后面的操作)
这里的影响指修改了 i 变量的值② 监视器锁规则: 对一个锁的解锁,happens-before 于随后对这个锁的加锁
③ volatile变量规则: 对一个 volatile域的写,happens-before于任意后续对这个volatile域的读
④ 传递性:如果 A happens-before B,且 B happens-before C,那么A happens-before C
看了这个公告,小张和小李满口称赞,这JMM还有两下子嘛,以后咱们就用这个规则了
happens-before之初体验
小李回来就迫不及待的写了一段代码感受一下happens-before,他在电脑上熟练的敲下了如下代码
int a = 1; // A操作
int b = 2; // B操作
int sum = a+b;// C 操作
System.out.println(sum);
然后他就启动一个线程把这段代码跑了,结果也是意料之中,为3
但是这次,在小李的脑子里已经有一个规则,就是happens-before规则,他清楚的知道,如果开一个线程跑这段代码,那么就会有以下的happens-before关系(根据上面的①和④)
1> A happens-before B
2> B happens-before C
3> A happens-before C
随便拿一个happens-before他都知道结果是什么,比如说A happens-before C,那再执行C操作(int sum = a+b)的时候,小李心里不用再担心sum有其他奇奇怪怪的结果了
他很确信在执行操作C的时候,A操作的影响(a变量最终会变为1)和B操作的影响操作C都知道[至于怎么知道不必深究]
善意的谎言
当小李把这段代码扔给Eclipse执行的时候,JMM这个时候开始清醒了,指导着JVM、编译器和硬件工作了
JMM发话了:“我不管你们用什么方法,只要不违反小李用的happesn-before原则和输出正确的结果,越快越好”
硬件发问了:“可以在执行的时候不按照他程序指定的顺序(程序逻辑顺序)来执行吗?”
JMM:“可以,但是你不能改变程序的结果啊”
硬件说道:“嗯嗯,肯定的,你看,int a = 1; 和 int b = 2;这两条语句无论先执行那条语句都可以
但是对于这两句我可以重排序一下,先执行int b = 2;再执行int a = 1;这样对于流水线执行指令的CPU,就可以加快处理速度了为什么会加快参考这篇文章”
老实的编译器说道:“那这不就欺骗了小李了吗?”
JMM:“我们这是善意的谎言,我们这么做事为他好呀,程序的结果又不错,程序的性能又提高了,何乐而不为呢?,对了,还有你,也跟硬件学着点,有什么法子变快就做”
最后的总结
执行完任务后,JMM准备给手底下的人科普一下程序员和他们以及自己的关系,随手就画了一个图
看到了吧,程序员看到的只是happens-before规则,咱们在底下做什么,他一概不知。咱们要做的就是,在满足happens-before可见性保证的前提下,尽情的优化。
直到有一天,小李看了这篇文章,才发现自己一直被蒙在鼓里,但是他还是开心的笑了。
原文:https://blog.csdn.net/qq_30137611/article/details/78146864?locationNum=4&fps=1