Volatile原理分析

volatile 用于提供顺序和可见性,volatile 类型变量即便在没有同步块的状况下赋值也不会与其余语句重排序,volatile 所修饰的变量的修改会马上写到主存去,解决了可见性的问题,concurrent 包中大量使用了 volatile 来修饰状态。

操做系统内存
    计算机在运行程序时,每条指令都是在 CPU 中执行的,在执行过程当中势必会涉及到数据的读写。咱们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有 CPU 中执行指令的速度快,若是任何的交互都须要与主存打交道则会大大影响效率,因此就有了 CPU 高速缓存。CPU 高速缓存为某个 CPU 独有,只与在该 CPU 运行的线程有关。
解决缓存一致性方案有两种:缓存

  • 经过在总线加 LOCK# 锁的方式。它是采用一种独占的方式来实现的,即总线加 LOCK# 锁的话,只能有一个 CPU 可以运行,其余 CPU 都得阻塞,效率较为低下。
  • 经过缓存一致性协议。缓存一致性协议(MESI协议)它确保每一个缓存中使用的共享变量的副本是一致的。其核心思想以下:当某个CPU 在写数据时,若是发现操做的变量是共享变量,则会通知其余 CPU 告知该变量的缓存行是无效的,所以其余 CPU 在读取该变量时,发现其无效会从新从主存中加载数据。

Java内存模型
    Java 内存模型规定全部的对象都是存在主存当中,每一个线程都有本身的工做内存。线程对变量的全部操做都必须在工做内存中进行,而不能直接对主存进行操做。而且每一个线程不能访问其余线程的工做内存。线程执行的时候用到某变量,首先要将变量从主内存拷贝的本身的工做内存空间,而后对变量进行操做:读取,修改,赋值等,这些均在工做内存完成,操做完成后再将变量写回主内存。
    Java 提供了 volatile 关键字来保证可见性。当一个共享变量被 volatile 修饰时,它会保证修改的值会当即被更新到主存,当有其余线程须要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,由于普通共享变量被修改以后,何时被写入主存是不肯定的,当其余线程去读取时,此时内存中可能仍是原来的旧值,所以没法保证可见性。

volatile实现原理
    volatile 在 JVM 底层是经过内存屏障(memory barrier)实现的。当你写一个 volatile 变量以前,Java 内存模型会插入一个写屏障(write barrier);读一个 volatile 变量以前,会插入一个读屏障(read barrier)。在你写一个 volatile 域时,能保证任何线程都能看到你写的值;在写以前,也能保证任何数值的更新对全部线程是可见的,由于内存屏障会将其余全部写的值更新到缓存。

内存屏障(meomory barrier)
序在运行时内存实际的访问顺序和程序代码编写的访问顺序不必定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提高程序运行时的性能。内存乱序访问主要发生在两个阶段:多线程

  1. 编译时,编译器优化致使内存乱序访问(指令重排);
  2. 运行时,多 CPU 间交互引发内存乱序访问。


内存屏障有两个能力:jvm

  1. 告诉编译器和 CPU,禁止屏障两边的指令重排序;
  2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。


volatile 保证 long 和 double 的原子性
    long 和 double 两种数据类型的操做可分为高32位和低32位两部分,对 long 和 double 的操做也是分两次完成。在64位机上使用64位的 jvm 中 long 和 double 的操做是原子的。因此,官方鼓励使用 volatile 修饰 long 和 double。

使用场景性能

  1. 标识状态;
  2. 一个线程写,多线程读;
  3. 使用锁进行全部变化的操做,使用 volatile进行只读操做。
相关文章
相关标签/搜索