java内存模型(JMM)与volatile

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/daguanjia11/article/details/81008294

java内存模型

jvm为了屏蔽掉各种硬件和操作系统之间的内存访问差异,定义了java内存模型。

java内存模型主要是定义了主内存(Main Memory)和工作内存(Working Memory)各自的职责:

  • 主内存直接对应物理硬件的内存。为了获得更快的性能,工作内存可能会优先使用寄存器和高速缓存。
  • 主内存是共享的,每个线程都有自己的工作内存。不同线程的工作内存是私有的,不能相互访问。
  • 所有变量都保存在主内存中。此处的变量,包括实例字段、静态字段构成数组对象的元素等。不包括局部变量和方法的参数(因为他们是线程私有的,没有共享问题)
  • 线程对变量的所有操作都要在工作内存中进行,而不能直接读写主内存中的变量。
  • 线程间的数据传递需要通过主内存来完成
  • 主内存主要对应于java堆中的对象实例数据部分,而工作内存主要对应虚拟机栈中的区域

也就是说,如果一个线程想要读取或修改java堆中的一个对象的字段的话,需要先从主内存中加载这个对象到自己的工作内存,如果线程对这个字段做了修改的话,还需要将最新的值会写到主内存。但是,如果这个对象在另一个线程中也需要读写的话,情况会变得更加复杂,如果一个线程修改了值:

  1. 不保证马上就把最新的值回写到主内存
  2. 不保证其它线程马上从主内存再读取一次最新的值

这样的话,在高并发的情况下,可能就会出现一些莫名其妙的问题。

你可能会问,我并没有考虑过这个问题,但我的代码没有出过这方面的问题呀?可能是你的代码的并发程度不够高,或者这种问题本来就是小概率事件吧。但你要明白jvm保证什么,不保证什么。保证的事情,就不会出问题,不保证的事情,就不排除出问题的可能性。

volatile的作用

volatile保证了什么

针对上面的问题,我们先来看看volatile关键字保证了什么

  1. 读取volatile类型的变量时,总是总主内存读取最新的值
  2. 修改volatile类型的变量时,总是立刻将最新的值同步写回主内存
  3. volatile关键字会形成一个内存栅栏,之前的代码编译出的指令一定早于volatile执行,之后的代码编译出的指令一定晚于volatile执行

前两条保证了变量在多线程并发的情况下也能保证变量的可见性。

为了提高代码的实际执行效率,编译器会在不影响代码原有意图的情况下,进行指令重拍的优化,也可能会出现莫名其妙的问题(情况非常少见),但使用volatile也可以避免这种问题,第3点就是保证了这个。

volatile不保证什么

volatile不保证并发时的线程安全。正如上面提到的情况,如果两个线程同时对一个volatile类型的数字进行累加的话,是线程不安全的,最终累加出来的值很不可靠。为什么呢?因为尽管线程每次都能拿到变量最新的值,但不能保证在你将要写入变量时,变量当前的最新值还是你计算时用的那个值。这种情况下是需要加锁的。

volatile适合什么场景下使用

volatile最适合的场景是:

  1. 有且只有一个线程更新变量,其余的所有线程读取变量
  2. 有多个线程会更新变量,但运算结果不依赖变量的当前值

第一种情况很好理解,只有一个线程更新变量的值,更新后马上刷回主内存,其余的线程每次都能从主内存读取到变量最新的值。
第二种情况可以这么理解,假设一个变量的默认值是false,并且这个变量被多个线程共享。其余线程在某个条件发生时(跟变量当前是true或false无关),将其更新为true。这种场景也无需加锁就能保证线程安全。

其他内存一致性的方式

如果要保证内存的一致性,除了volatile方式以外,还有两种比较常用的方式

  1. 定义为final。final变量在初始化以后就不可能再发生变化了,没有变化自然就安全了
  2. 加锁。使用synchronizedLock都可以。锁的机制保证了两点,获取锁后从主内存读取最新的数据,以及,释放锁之前将变化的变量写回主内存。

猜你喜欢

转载自blog.csdn.net/daguanjia11/article/details/81008294