并发三:同步原语final、volatile的内存语义

先介绍两个概念this逃逸和内存屏障

this逃逸
一个小栗子:

this逃逸示例

步骤1和步骤2之前没有重排序的限制规定,因此这两个操作是可以重排序的。如果出现了重排序那么执行”read”方法的线程就有可能读到final变量初始化之前的值,造成final变量未正确初始化。

另外在构造函数中注册事件监听,在构造函数中启动新线程都有可能会引发”this逃逸”。

在构造函数返回之前,其他线程就持有了该对象的引用,这种情况就叫this逃逸。调用尚未构造完成的对象的方法可能引发错误。

内存屏障指

内存屏障(memory barriers)也成为内存栅栏,是一组处理器指令,对内存操作的顺序进行限制,大多数现代计算机为了提高性能而采取乱序执行这使得内存屏障成为必须。JMM内存语义底层是通过memory barriers实现的。

1、LoadLoad Barriers:示意指令"Load1; LoadLoad; Load2",保证Load1先读取完毕,再执行Load2及后续读取指令。

2、StoreStore Barriers:示意指令"Store1; StoreStore; Store2",保证Store1先刷新到内存(对其他处理器可见),再执行Store2及后续写入操作。

3、LoadStore Barriers:示意指令"Load1; LoadStore; Store2",保证Load1先读取完毕,再执行Store2及后续写入指令刷新到内存。

4、StoreLoad Barriers:示意指令"Store1; StoreLoad; Load2",保证Store1先刷新到内存(对其他处理器可见),再执行Load2及后续读取指令。

在大多是处理器中,会先执行完屏障前的所有读写指令,再执行屏障后的内存访问指令。

final语义

重排序

JMM对final的重排序做了特殊的规定,并且在JSR-133做了增强,编译器和处理器在对final域进行重排序时候,都要遵循如下规则:

1、在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

禁止编译器重排序:编译器不能将final域的写操作重排序到构造函数之外;

禁止处理器重排序:编译器会在final与写之后,构造函数return之前插入个StoreStore屏障,用来禁止处理器将final域的写操作重排序到构造函数之外;

2、初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

读对象引用,读对象的final域 这两个操作不能重排序。

3、在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。如下代码s1处和s2禁止进行重排序:

可见性

final修饰的变量作为不可变变量,只要对象是正确构造的(没有this逃逸发生),不需要任何同步措施就可以保证任何线程都能读到变量在构造函数中被初始化之后的值。

volatile语义

原子性

volatile是否保障原子性是个有争议的话题,32位的JDK中volatile修饰后的long和double也具有原子性。但是volatile int i=0;i++;就不具备原子性,因此可以理解为对单次volatile变量的读写操作具有原子性,复合操作(如i++)不具有原子性。

重排序

为了实现volatile的语义,JMM限制了volatile的编译器重排序和处理器重排序,volatile变量编译器重排序规则:

编译器会在volatile写操作前后插入StoreStore Barriers和StoreLoad Barriers,volatile读操作前后插入LoadLoad Barriers和LoadStore Barriers。通过插入内存屏障来限制处理器对volatile进行重排序。

volatile与可见性

通过volatile读写操作前后所插入的memory barriers就能够看出,volatile变量能够保证可见性。

小结

1、JMM保证final变量初始化时的有序性、禁止编译器和处理器重排序。
2、final作为不可变对象,正确初始化后(没有this逃逸),能够保障可见性。
3、volatile能够保障单次操作的原子性
4、volatile能够保障变量的可见性

并发专题目录贴

码字不易,转载请保留原文连接https://www.jianshu.com/p/aa432a918db9

推荐阅读更多精彩内容

  • 此文为转载: 转载地址放在链接中:原文发表地址 整理 by 微凉季节 评价:从多线程引出处理器内存,再杀到总线仲裁...
    topwqp阅读 2,799评论 0 62
  • 目录: 1. 指令重排 2. 顺序一致性 3. volatile 4. final 1.指令重排 要了解指令重排,...
    西部小笼包阅读 163评论 0 1
  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 77,500评论 25 510
  • 内存屏障是硬件之上、操作系统或JVM之下,对并发作出的最后一层支持。再向下是是硬件提供的支持;向上是操作系统或JV...
    猴子007阅读 11,376评论 6 37
  • 并发系列的文章都是根据阅读《Java 并发编程的艺术》这本书总结而来,想更深入学习的同学可以自行购买此书进行学习。...
    小之丶阅读 476评论 0 6