【java并发编程】volatile关键字

一、简介

保证线程可见性,有序性,但不保证原子性(voilatile是cpu级别的缓存锁,java层面可以不需要关心它,因此需要加上cas才能保证线程安全性)。

虽然voilatile不能保证线程安全,但是适用于一个线程写,多个线程读的情况。确保线程每次读取,都是读的内存中的实时值,而不是寄存器的旧值。

二、JMM内存模型

全称java memory model中文名为java内存模型。

因为在不同的硬件生产商和不同的操作系统下,内存的访问有一定的差异,所以会造成相同的代码运行在不同的系统上会出现各种问题。所以java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。

JMM内存模型如图:

每个java线程都有自己的工作内存,线程需要操作哪个变量,需要从主内存中load到自己的工作内存中才能使用。

JMM原子操作

三、内存屏障

硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障有两个作用:

  • 阻止屏障两侧的指令重排序;
  • 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

java中的内存屏障

通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。

  1. LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  2. StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  3. LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  4. StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

四、volatile关键字作用

1、保证并发编程的可见性(不保证原子性)

单线程下修改读取数据时Java内存模型没问题;但是多线程下可能读取到脏数据,需要用volatile修饰
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新的值

实现可见性原理

底层实现主要是通过汇编lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定) 并回写到主内存

1A-32和Intel 64架构软件开发者手册对lock指令的解释:

  1. 会将当前处理器缓存行的数据立即写回到系统内存。
  2. 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效(MESI协议)
  3. 提供内存屏障功能,使lock前后指令不能重排序

查看反汇编代码

-server -Xcomp -XX:+ UnlockDiagnosticYMOptions -XX:+ PrintAssembly XX:CompileCommand=compileonly,*VolatileVisibilityTest.prepareData

2、禁止指令重排序

指令的执行顺序并不一定会像我们编写的顺序那样执行,为了保证执行上的效率,JVM(包括CPU)可能会对指令进行重排序。单线程时重排序无影响,但是多线程时顺序错了,可能就不符合需求了,需要保证执行顺序的变量可以用volatile修饰。


典型例子:单例模式的懒汉式双重检测加锁实现方式

参考:java设计模式之单例模式singleton_现实、太残忍的博客-CSDN博客

指令重排序遵循两种原则

as-if-serial原则

不管怎么重排序(编译器和处理器为了提供并行度),(单线程)程序的执行结果不能被改变。

happen-before原则

happen-before 关系是用来判断是否存在数据竞争、线程是否安全的主要依据,也是指令重排序的依据,保证了多线程下的可见性。
volatile 修饰的变量在读写时会建立 happen-before 关系。

as-if-serial与happen-before区别

  • as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
  • as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
  • as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。

实现防止指令重排序原理

在每个volatile写操作前插入StoreStore屏障,这样就能让其他线程修改A变量后,把修改的值对当前线程可见,在写操作后插入StoreLoad屏障,这样就能让其他线程获取A变量的时候,能够获取到已经被当前线程修改的值,在每个volatile读操作前插入LoadLoad屏障,这样就能让当前线程获取A变量的时候,保证其他线程也都能获取到相同的值,这样所有的线程读取的数据就一样了,在读操作后插入LoadStore屏障;这样就能让当前线程在其他线程修改A变量的值之前,获取到主内存里面A变量的的值。

猜你喜欢

转载自blog.csdn.net/sumengnan/article/details/125063565