volatile底层原理详解

今天咱们聊聊volatile底层原理;java

Java语言规范对于volatile定义以下:编程

Java编程语言容许线程访问共享变量,为了确保共享变量可以被准确和一致性地更新,线程应该确保经过排它锁单独得到这个变量。缓存

首先咱们从定义开始入手,官方定义比较拗口。通俗来讲就是一个字段被volatile修饰,Java的内存模型确保全部的线程看到的这个变量值是一致的,可是它并不能保证多线程的原子操做。这就是所谓的线程可见性。咱们要知道他是不能保证原子性的多线程

内存模型相关概念

Java线程之间的通讯由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的修改什么时候对另一个线程可见。JMM定义了线程与主内存的抽象关系:线程之间的变量存储在主内存(Main Memory)中,每一个线程都有一个私有的本地内存(Local Memory)保存着共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。编程语言


若是线程A与线程B通讯:spa

  1. 线程A要先把本地内存A中更新过的共享变量刷写到主内存中。线程

  2. 线程B到主内存中读取线程A更新后的共享变量code

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

有了CPU高速缓存虽然解决了效率问题,可是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所须要的数据复制一份到CPU高速缓存中,在进行运算时CPU再也不也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中。cdn

举个例子:

i++;

当线程运行这行代码时,首先会从主内存中读取i,而后复制一份到CPU高速缓存中,接着CPU执行+1的操做,再将+1后的数据写在缓存中,最后一步才是刷新到主内存中。在单线程时没有问题,多线程就有问题了。

以下:假若有两个线程A、B都执行这个操做(i++),按照咱们正常的逻辑思惟主存中的i值应该=3,但事实是这样么?

分析以下:

两个线程从主存中读取i的值(1)到各自的高速缓存中,而后线程A执行+1操做并将结果写入高速缓存中,最后写入主存中,此时主存i==2,线程B作一样的操做,主存中的i仍然=2。因此最终结果为2并非3。这种现象就是缓存一致性问题。

解决缓存一致性方案有两种:

  1. 经过在总线加LOCK#锁的方式;

  2. 经过缓存一致性协议。

可是方案1存在一个问题,它是采用一种独占的方式来实现的,即总线加LOCK#锁的话,只能有一个CPU可以运行,其余CPU都得阻塞,效率较为低下。

第二种方案,缓存一致性协议(MESI协议)它确保每一个缓存中使用的共享变量的副本是一致的。因此JMM就解决这个问题。

volatile实现原理

有volatile修饰的共享变量进行写操做的时候会多出Lock前缀的指令,该指令在多核处理器下会引起两件事情。

  1. 将当前处理器缓存行数据刷写到系统主内存。

  2. 这个刷写回主内存的操做会使其余CPU缓存的该共享变量内存地址的数据无效。

这样就保证了多个处理器的缓存是一致的,对应的处理器发现本身缓存行对应的内存地址被修改,就会将当前处理器缓存行设置无效状态,当处理器对这个数据进行修改操做的时候会从新从主内存中把数据读取到缓存里。

使用场景

volatile常常用于两个场景:状态标记、double check

  1. 状态标记
//线程1
boolean stop = false;
while(!stop){
   doSomething();
}

//线程2
stop = true;
复制代码

这段代码是很典型的一段代码,不少人在中断线程时可能都会采用这种标记办法。可是事实上,这段代码会彻底运行正确么?即必定会将线程中断么?不必定,也许在大多数时候,这个代码可以把线程中断,可是也有可能会致使没法中断线程(虽然这个可能性很小,可是只要一旦发生这种状况就会形成死循环了)。

下面解释一下这段代码为什么有可能致使没法中断线程。在前面已经解释过,每一个线程在运行过程当中都有本身的工做内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在本身的工做内存当中。

那么当线程2更改了stop变量的值以后,可是还没来得及写入主存当中,线程2转去作其余事情了,那么线程1因为不知道线程2对stop变量的更改,所以还会一直循环下去。

可是加上volatile就没问题了。以下所示:

volatile boolean flag = false;

    while(!flag){
       doSomething();
    }

    public void setFlag() {
       flag = true;
    }

    volatile boolean inited = false;
    //线程1:
    context = loadContext();  
    inited = true;            

    //线程2:
    while(!inited ){
    sleep()
    }
    doSomethingwithconfig(context);
复制代码
  1. double check
public class Singleton{
   private volatile static Singleton instance = null;

   private Singleton() {

  }

   public static Singleton getInstance() {
       if(instance==null) {
           synchronized (Singleton.class) {
               if(instance==null)
                   instance = new Singleton();
          }
      }
       return instance;
  }
}
复制代码

客官以为有用请点赞或收藏,关注公众号JavaStorm,你将发现一个有趣的灵魂!
后面咱们继续分析JMM内存模型相关技术。

将本身的知识分享,之后会持续输出,但愿给读者朋友们带来帮助。如有帮助读者朋友能够点赞或者关注。

JavaStorm.png
相关文章
相关标签/搜索