instance = new Singleton() //instance 是volatile 变量
转变成汇编代码,会多出lock ...
Lock 前缀的指令在多核处理器下会引发了两件事情:
- 将当前处理器缓存行的数据写回到系统内存
- 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效
volatile写-读的内存语义
- volatile 写的内存语义
当写一个volatile 变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。 - volatile 读的内存语义
当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从内存中读取共享变量。
总结
- 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的线程发出了(其对共享变量所做修改的)消息
- 线程B读一个 volatile 变量,实质上是线程B接受了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改)的消息
- 线程A写一个 volatile 变量,随后线程B读这个 volatile 变量,这个过程实质上是线程A通过主内存向线程B发送消息。
volatile 内存语义的实现
是否能重排序 | 第二个操作 | ||
---|---|---|---|
第一个操作 | 普通读/写 | volatile 读 | volatile 写 |
普通读/写 | NO | ||
volatile 读 | NO | NO | NO |
volatile 写 | NO | NO |
举例:第三行最后一个单元格的意思是:在程序中,当第一个操作位普通变量的读或写时,如果第二个操作位volatile写,则编译器不能重排序这两个操作。
- 从上表我们可以看出
- 当第二个操作是 volatile 写时,不管第一个操作是什么,都不能重排序。这个规则确保 volatile 写之前的操作不会被编译器重排序到 volatile 写之后。
- 当地一个操作是volatile 读时,不管第二个操作是什么,都不能重排序。这个规则确保 volatile 读之后不会被编译器重排序到 volatile 读之前。
- 当第一个操作是 volatile 写,第二个操作是 volatile 读时,不能重排序。
为了实现 volatile 的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略。
- 在每个volatile写的前面插入一个StoreStore 屏障
- 在每个volatile写的后面插入一个StoreLoad 屏障
- 在每个volatile读的前面插入一个LoadLoad屏障
- 在每个volatile读的后面插入一个LoadStore屏障
网友评论