JMM的设计思想:先保证正确性,然后尽量优化保证执行性能
JMM的设计意图
程序员对内存模型的要求,易于理解,易于编程,希望内存模型足够强,足有严格。
编译器和处理器为了尽可能地提高并行度,希望内存模型的约束越少越好。
对于会改变程序执行结果的重排序,JMM要求编译器和处理器禁止重排序;
对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求。
1.volatile的内存语义与实现 1.1 volatile写-读建立的happens before关系volatile写happens before接下来的volatile读。一个volatile变量的写 *** 作,总是能被接下来的读 *** 作可见。
1.2 volatile的内存语义volatile的写-读与锁的释放-获取有相同的内存效果,volatile写和锁释放有相同的内存语义,volatile读与锁获取有相同的内存语义。
volatile写的内存语义,当写一个volatile变量时,JMM会把当前线程中volatile写之前的共享变量从本地内存刷新到主内存中,同时也会把这个volatile变量刷新到主内存中。
volatile读的内存语义,当读取一个volatile变量时,JMM会把当前线程中volatile变量和volatile读后边的共享变量对应的本地内存置为无效,然后从主内存中读取最新的值到本地内存。
一个线程写volatile变量后,写之前的共享变量和这个volatile变量会对另一个接下来读这个volatile变量的线程立即可见。
1.3 volatile内存语义的实现JMM为编译器指定了严谨的volatile重排序规则。
volatile写之前的 *** 作,不能重排序;
volatile读之后的 *** 作,不能重排序;
先volatile写,后volatile读,不能重排序。
是否重排序
第二个 *** 作
第一个 *** 作
普通读/写
volatile读
volatile写
普通读/写
No
volatile读
No
No
No
volatile写
No
No
为了实现volatile的内存语义,编译器在实现指令序列时,在适当的位置插入内存屏障指令来禁止特定的处理器重排序。
在每个volatile写之前插入StoreStore;禁止和之前的普通读/写重排序,禁止和之前的volatile读/写重排序
在每个volatile写之后插入StoreLoad;禁止和之后volatile读重排序
在每个volatile读之后插入LoadStore;禁止和之后的普通写/volatile写重排序
在每个volatile读之后插入LoadLoad;禁止和之后的普通读/volatile读重排序
JMM先采用保守策略,先插入全部内存屏障指令,然后编译器针对特定平台的CPU进行优化,去掉多余的内存屏障指令。
1.4 volatile内存语义被加强旧版volatile的写-读没有锁释放-获取的内存语义;为了提供比锁轻量级的线程通信机制,对volatile的内存语义进行加强。注意直接通过读写volatile,实现不了锁的互斥性。
2.锁的内存语义与实现锁可以实现临界区代码互斥执行。锁是Java并发编程中最重要的同步方式,同一个时间点只有一个线程能获取到锁。
2.1 锁释放-获取建立的happens before关系锁释放happens before锁获取。
2.2 锁的内存语义锁释放的内存语义,一个线程释放锁后,JMM会把这个线程对应的本地内存刷新到主内存中。
锁获取的内存语义,一个线程获取锁后,JMM会把这个线程对应的本地内存置为无效,使得访问临界区内的共享变量重新从主内存中获取。
2.3 CAS指令特性(1)对内存的读改写 *** 作具有原子性;
(2)禁止CAS指令和之前的读、写 *** 作重排序,且禁止CAS指令和之后的读、写 *** 作重排序;
(3)把写缓冲区中的所有数据刷新到内存中。
CAS的同时具有volatile读和volatile写的内存语义。
2.4 可重入锁内存语义的实现公平锁模式下
锁获取,核心 *** 作是读去volatile类型的state;
锁释放,核心 *** 作是写volatile类型的state。
非公平锁模式下
锁获取,核心 *** 作是CAS volatile类型的state;
锁释放,核心 *** 作是写volatile类型的state。
3.final域的内存语义对于final域,编译器和处理器遵守的重排序规则:
(1)在构造函数内对一个对象的final域的写入,与随后把这个被构造的对象的引用赋值给一个引用变量,这两个 *** 作不能重排序;
(2)对于final域为对象引用类型时,在构造函数内对final域引用对象的成员的写入,与随后把这个被构造的对象的引用赋值给一个引用变量,这两个 *** 作不能重排序;
(3)首次读含有final域的对象的引用,与随后首次读这个对象的final域,这两个 *** 作不能重排序。
写final域的重排序规则保证,构造函数中的final域写入 *** 作和final域引用对象的成员写入 *** 作不会被重排序到构造函数之外,对象的引用对线程可见之前,对象的final域和final域引用对象的成员都已经正确初始化。
注意:对象的普通域,在构造函数中的写入 *** 作(初始化)可能会被重排序到构造函数之外。
读final域的重排序规则保证,在读一个对象的final域之前,一定会先读包含这个final域对象的引用。即对一个对象的final域的读取,读到的一定是初始化好的值。
注意:不要在构造函数中将this指针赋值给静态成员引用变量,避免造成final域的初始化 *** 作从构造函数中“溢出”,(实际上并不是final域的写 *** 作重排序到了构造函数外边,而是这个对象的引用过早的被其他线程可见)。
3.1 final域内存语义的实现写final域的实现,在构造函数中,在写final域之后和构造函数return之前,插入StoreStore内存屏障。
读final域的实现,在读final域之前插入LoadLoad内存屏障。
4.happens-before 4.1 happens-before关系的定义(1)如果一个 *** 作happens-before另一个 *** 作,那么第一个 *** 作的结果对第二个 *** 作的结果可见,第一个 *** 作排在第二个 *** 作前执行;
(2)如果两个 *** 作之间存在happens-before关系,如果重排序不改变执行结果,JMM允许这两个 *** 作发生重排。
as-if-serial原则保证,在单线程内程序的执行结果不会被重排序改变;happens-before原则保证,在多线程中,增加正确的同步措施后,程序的执行结果不会被重排序改变。
as-if-serial原则和happens-before原则具有相同的目标。两个的初衷都是在不改变程序执行结果的前提下,尽可能地提高程序(指令)执行的并行度。
4.2 happens-before规则(1)程序顺序规则,在一个线程中,任意一个 *** 作happens-before后续任意多个 *** 作。
(2)监视器锁规则,监视器锁释放happens-before随后的监视器锁获取。
(3)volatile规则,对一个volatile域的写happens-before随后任意多个对这个volatile域的读。
(4)线程start规则,如果在线程A中执行启动线程B,即执行ThreadB.start(),则线程A中的ThreadB.start() *** 作happens-before线程B中的任意 *** 作。
(5)线程join规则,如果在线程A中等待线程B的执行结果,即执行ThreadB.join()并成功返回,则线程B的任意 *** 作happens-before线程A从ThreadB.join()执行返回。
(6)传递性规则,A *** 作happens-beforeB *** 作,B *** 作happens-beforeC *** 作,则A *** 作happens-beforeC *** 作。
参考资料《Java并发编程的艺术》 机械工业出版社 方腾飞 魏鹏 程晓明
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)