volatile底层原理

java并发编程中,经常会看到 volatile 关键字。今天就让我们来盘一盘它。

volatile相关定义

java语言规范对于 volatile 定义如下:
java编程语言允许线程访问共享变量,为了确保共享变量能偶被准确和一致性地更新,线程应该确保通过排它锁单独获得这个变量。

通俗讲,一个字段被 volatile 关键字修饰,java的内存模型却被所有的线程看到的这个变量值是一致的,但是它并不能保证多线程的原子操作。这就是所谓的线程可见性,但不保证原子性。

Java共享变量的内存可见性问题

Java内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作内存,然后对工作内存里的变量进行处理,处理完后将变量值更新到主内存。

Java内存模型是一个抽象的概念,那么实际实现中工作内存类似于CPU的高速缓存,下面是一个双核CPU的系统架构。

如图所示,每个内核都有自己的控制器和运算器,其中控制器包含一组寄存器和操作控制器,运算器执行算术逻辑运算。每个核都有自己的一级缓存,在有些架构里面还有一个所有CPU都共享的二级缓存。

Java内存模型里面的工作内存,就对应这里的L1或者L2缓存或者CPU的寄存器。

在上图架构基础上,如果线程A和线程B同时处理一个共享变量,会出现什么情况?假设线程A和线程B使用不同CPU执行,并且当前量级Cache都为空,那么这时候由于Cache的存在,将会导致内存不可见问题,具体分析如下:

  • 线程A首先获取共享变量X的值,由于L1,L2Cache都没有命中,所以加载主内存中X的值,假如为0.然后把X=0的值缓存到L1,L2Cache,线程A修改X的值为1,然后将其写入量级Cache,并且刷新到主内存。线程A操作完毕后,线程A所在的CPU的两级Cache内合主内存里面的X的值都是1。
  • 线程B获取X的值,首先一级缓存没有命中,然后看二级缓存,二级缓存命中了,所以返回X=1;到此处一切正常,因为这时候主内存中X=1。然后线程B修改X的值为2,并将其存到线程2所在的一级Cache和共享二级Cache中,最后更新主内存中X的值为2;到这里一切都是好的。
  • 线程A再次需要修改X的值,获取一级缓存命中,并且X=1,到这里问题就出现了,命名线程B已经把X的值修改为了2,为何线程A获取的还是1呢?这就是共享变量的内存不可见问题,也就是线程B写入的值对线程A不可见。

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

  1. 通过在总线加LOCK#锁的方式;
  2. 通过缓存一致性协议。

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

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

volatile实现原理

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

  1. 讲当前处理器缓存行数据刷写到系统驻内存。
  2. 这个刷写回主内存的操作会使其他CPU缓存的该共享变量内存地址的数据无效。

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

volatile的第一个特性--保证可见性

解决内存可见性问题方式的一种是加锁,但是使用锁太笨重,因为它会带来线程上下文的切换开销。Java提供了一种弱形式的同步,也就是volatile关键字。该关键字确保对一个变量的更新对其他线程马上可见。

当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。

当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。

理解volatile保证可见性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步。

volatile的第二个特性--保证有序性

Java内存模型允许编译器和处理器对指令重排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。

什么是数据依赖性?
如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。

在单线程下重排序可以保证最终执行结果与程序顺序执行的结果一致,但是在多线程下就会出现问题。

使用场景

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

状态标记

// 线程
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);

double check

我们可以放心的使用DCL(double-checked locking)实现单例模式。

public class Singleton2 {
/**
* 双重锁机制+volatile关键字实现单例
*/
//声明单例对象
private static volatile Singleton2 instance;

//私有化构造器
private Singleton2(){

}
//双重检查加锁
public static Singleton2 getInstance(){
    if (instance == null) {
        synchronized (Singleton2.class) {
            if (instance == null) {
                instance=new Singleton2();
                }
        }
    }
    return instance;
}

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容