volatile的几个灵魂拷问

要想讲清楚volatile关键字,这时候就应该主动从内存模型开始讲起,然后说原子性、可见性、有序性的理解,铺垫好这些才是到volatile关键字的原理,假定前面一篇内存模型的文章已经理解到位了,那么这里直接从volatile的原理开搞!

首先说结论,volatile关键字作用的是保证可见性和有序性,并不保证原子性。只有synchronized关键字同时保证上述三种特性。但是volatile是轻量级的synchronized,它在多线程中保证了变量的“可见性”。可见性的意思是当一个线程修改了一个变量的值后,另外的线程能够读取到这个变量修改后的值。

1、保证可见性

用 volatile 关键字修饰的共享变量,编译成字节码后增加 Lock 前缀指令,该指令要做两件事:

  • 将当前工作内存缓存行的数据立即写回到主内存。
  • 写回主内存的操作会使其他工作内存里缓存了该共享变量地址的数据无效(缓存一致性协议保证的操作)。

对volatile修饰的变量,执行写操作的话,JVM会发送一条lock前缀指令给CPU,CPU在计算完之后会立即将这个值写回主内存,同时因为有MESI缓存一致性协议,所以各个CPU都会对总线进行嗅探,自己本地缓存中的数据是否被别人修改。

如果发现别人修改了某个缓存的数据,那么CPU就会将自己本地缓存的数据过期掉,然后这个CPU上执行的线程在读取那个变量的时候,就会从主内存重新加载最新的数据了。

所以,volatile的可见性就是:lock前缀指令 + MESI缓存一致性协议

Java程序打印成汇编语句,引用自 https://zhuanlan.zhihu.com/p/77085695

2、保证有序性

Lock 前缀指令有内存屏障的作用。上一篇文章已经提到过了,JMM一共有4种内存屏障,分别是 LoadLoad、LoadStore、StoreStore、StoreLoad。JMM 给volatile插入内存屏障保守策略:

  • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障,后面插入一个 StoreLoad 屏障。
  • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障和一个 LoadStore 屏障。
linux系统下x86架构的实现

StoreStore屏障可以保证在volatile写之前,前面所有的普通读写操作同步到主内存中
StoreLoad屏障可以保证防止前面的volatile写和后面有可能出现的volatile度/写进行重排序
LoadLoad屏障可以保证防止下面的普通读操作和上面的volatile读进行重排序
LoadStore屏障可以保存防止下面的普通写操作和上面的volatile读进行重排序

3、不具备原子性

volatile不具备原子性,多个线程去写同一个公共volatile修饰的变量会出现线程安全问题,对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对32位操作。如果使用volatile修饰long和double,那么其读写都是原子操作,这里可以稍稍注意下。

由于volatile不具备原子性,因此还需要有一个原子性能力的补充,这里就可以看看这个文章进行回顾下:《CAS是什么?Atomic包知多少?》

4、和Synchronized的内存屏障差别

按照可见性保障来划分。内存屏障可分为加载屏障(Load Barrier)和存储屏障(Store Barrier)。加载屏障的作用是刷新处理器缓存,存储屏障的作用冲刷处理器缓存。Java虚拟机会在 MonitorExit ( 释放锁 ) 对应的机器码指令之后插入一个存储屏障,这就保障了写线程在释放锁之前在临界区中对共享变量所做的更新对读线程的执行处理器来说是可同步的。相应地,Java 虚拟机会在 MonitorEnter ( 申请锁 ) 对应的机器码指令之后临界区开始之前的地方插入一个加载屏障,这使得读线程的执行处理器能够将写线程对相应共享变量所做的更新从其他处理器同步到该处理器的高速缓存中。因此,可见性的保障是通过写线程和读线程成对地使用存储屏障和加载屏障实现的。

按照有序性保障来划分,内存屏障可以分为获取屏障(Acquire Barrier)和释放屏障 ( Release Barrier )。获 取 屏 障 的 使 用 方 式 是 在 一 个 读 操 作 ( 包括 Read-Modify-Write 以及普通的读操作 )之后插入该内存屏障,其作用是禁止该读操作与其后的任何读写操作之间进行重排序,这相当于在进行后续操作之前先要获得相应共享数据的所有权 ( 这也是该屏障的名称来源 )。释放屏障的使用方式是在一个写操作之前插入该内存屏障,其作用是禁止该写操作与其前面的任何读写操作之间进行重排序。这相当于在对相应共享数据操作结束后释放所有权( 这也是该屏障的名称来源 )。 Java虚拟机会在 MonitorEnter( 它包含了读操作 ) 对应的机器码指令之后临界区开始之前的地方插入一个获取屏障,并在临界区结束之后 MonitorExit ( 它包含了写操作 ) 对应的机器码指令之前的地方插入一个释放屏障。因此,这两种屏障就像是三明治的两层面包片把火腿夹住一样把临界区中的代码(指令序列)包括起来:

可以发现,与volatile类似,synchronized底层也是通过释放屏障和获取屏障的配对使用保障有序性,加载屏障和存储屏障的配对使用保障可见性。最后又通过锁的排他性保障了原子性。

参考文献

1、volatile底层原理详解
2、synchronized 实现原理与内存屏障

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