程序员,请善待内存(三)--- JMM
上集说到基于MESI缓存一致性协议的Java内存模型JMM,在MESI基础上,为了提升效率,允许指令重排序,但是引入了Java在语义方面的问题。
首先,我们写的Java源码代码生成Java虚拟机可以执行指令序列,需要经历下面几个步骤:
imageJava程序员会有疑问,我的代码会被重排序,那我要实现的逻辑和预期不一致了?
image假如没有JMM,java程序员GG写遇到下面的情况,出现与预期不一致,100%懵逼
情况一:
数据有依赖性:(针对的是单处理单线程的数据)
- 写后读
a=1 b=a
- 写后写
a=1 a=2
- 读后写
a=b b=1
这三种操作一旦被处理器重排序,结果就会被改变
情况二:
1 double pi = 3.14;
2 double r = 1.0;
3 double area = pi *r*r;
程序员的角度看,执行顺序一定是按顺序执行的,但是实际上,没有JMM的情况下,1和3有数据依赖关系,2和3有数据依赖关系,1和2的顺序因为不同处理器的重排序而执行的先后顺序不一致
JMM是java程序员使用的内存模型,在程序员的角度,内存一致性是要保证的,不需要关心不同编译器,不同处理器的重排序处理。这里,JMM需要通过内存屏障来禁止不同编译器,不同处理器的重排序行为。(遵循Snoopy协议)
什么情况下JMM会使用内存屏障呢?Store与Load操作之间,全能型屏障,而且大多数处理器都支持
JMM要为程序员们保证内存可见性。需要一套准则来保证,那就是happens-before,这个类似于JMM给程序的一种承诺。
image那么,java程序员只要记住容易理解的happens-before原则,不去理会JMM的具体处理,放心地操控每一种硬件资源。happens-before是JMM的关键,既要程序员要求的可见性,又要减少对硬件资源的束缚。
happens before准则
happens before准则,就像我们小时候学的乘法口诀,简单好记,不用了解乘法运算具体怎么定义怎么算,掌握了它,就可以愉快的算数~
具体规则如下,敲黑板,要牢记~
. 规则一:程序的顺序性规则
一个线程中,按照程序的顺序,前面的操作happens-before后续的任何操作。
对于这一点,可能会有疑问。顺序性是指,我们可以按照顺序推演程序的执行结果,但是编译器未必一定会按照这个顺序编译,但是编译器保证结果一定==顺序推演的结果。
- 规则二:volatile规则
对一个volatile变量的写操作,happens-before后续对这个变量的读操作。
- 规则三:传递性规则
如果A happens-before B,B happens-before C,那么A happens-before C。
4.规则四:管程中的锁规则
对一个锁的解锁操作,happens-before后续对这个锁的加锁操作。这一点不难理解。
5.规则五:线程start()规则
主线程A启动线程B,线程B中可以看到主线程启动B之前的操作。也就是start() happens before 线程B中的操作。
6.规则六:线程join()规则
主线程A等待子线程B完成,当子线程B执行完毕后,主线程A可以看到线程B的所有操作。也就是说,子线程B中的任意操作,happens-before join()的返回。
嗯,JMM的核心就是几条happens-before了,是volatile,synchronized,final的内存语义基础,再上一层,是java并发的基础,其实知识就是套娃,知道它是怎么来的,是什么,解决了什么问题,缺点是什么,脉络清楚。相信有一天会质变。日拱一卒,就要这里,明天继续来分析,几个java关键字的语义,奥利给
image.png