Volatile关键字详解

volatile是Java中一个非常重要的关键字,要想完全搞懂volatile的作用,需要一些额外的辅助知识。

要了解volatile,就要先对CPU,CPU cache和主存有所有了解。

主存就是我们平时说的内存,它的读写速度很慢,而CPU运算速度很快,为了弥补二者的差异,在CPU和主存之间引入了CPU Cache,
现在很多CPU都会有三层Cache,分别称为L1,L2和L3,其中L1最靠近CPU,缓存速度也最快,L1这块缓存被分为了两个部分,一部分是指令缓存,称为L1i,一部分是数据缓存,称为L1d.

只有有缓存,就会出现缓存不一致的问题,比如i++这个操作。

在有缓存时候的计算流程是:

  1. 从主存中读取i的值到缓存中
  2. 在缓存中把i的值加1
  3. 把缓存中的值刷回到主存中。

这样的模型在单线程下没有任何问题,但是在多线程中,就会出现问题,比如在步骤二中执行,此时有一个线程直接给i重新赋值了,这时候就会出现问题。

解决这个问题的一个有效办法就是 缓存一致性协议,这个协议大体思路是:

  1. 当CPU操作Cache中的副本时,会去查看该副本是不是在其他Cache中也存在一份
  2. 如果不存在的话,CPU就可以对Cached中的副本进行任意的操作了
  3. 如果存在的话, CPU先把Cached中的副本读取到自己的寄存器中,然后通知其他的CPU说"你们保存的Cache无效了"
  4. 其他CPU收到这个通知之后,如果要操作这个变量的副本,就会从主存里在读取一遍。

那么我们接下来看一下Java的内存模型,Java的内存模型一个很重要的作用就是规定了:一个线程对共享变量(主内存中的变量)的修改,何时对其他线程可见。

具体来说就是:

  1. 每个线程都有自己的工作内存,称为缓存。
  2. 线程只能操作自己的工作内存,不能直接操作主内存
  3. 共享变量在工作内存中有一个副本
  4. JVM决定何时把工作内存中的副本刷回主内存。

接下来了解一下并发编程必须要掌握的几个概念:

  1. 原子性
    一组操作要么不做,要么保证全部做完,不会被中断
    volatile关键字无法保证原子性

  2. 可见性
    一个线程对一个变量进行了修改,另一个线程可以马上感知到这种修改。
    volatile关键字可以保证可见性

  3. 有序性
    主要就是防止进行指令重排,因为为了优化执行顺序,JVM并不一定会按照书写的顺序去执行,会进行优化,优化的原则是:不保证执行的顺序,只保证重排之后的执行结果和重排之前一样。

在多线程情况下,指令重排有时候会造成问题。所以有时候要禁止掉指令重排。

最常见的就是通过一个变量来进行初始化判断,比如

private boolean isInit = false;
private Manager mManger;
public Manager getManager(){
  if(!isInit){
    mManger = initManager();
    isInit = true;
  }
  return mManager;
}

在上面那个例子中,单线程情况下不会有任何问题,在多线程情况下,如果不进行指令重排也不会有问题。
但是如果进行了指令重排,比如指令重排之后,把isInit = true放在了mManager = initManager()之上,很可能在多线程的情况下出现mManager为空的情况,从而出现空指针异常。而且这种异常还很难发现,通常大家都是一脸懵逼,说mManager肯定不可能为空呀。

那现在我们把多线程情况下的指令重排造成的mManager为空的情况说一下。

线程1执行getManager方法,由于指令重排,isInit = true先执行,然后去真正初始化mManager,最后返回mManager实例,不会有任何问题。
但是当线程1执行完isInit = true之后,线程2开始执行getManager方法,发现isInit为true,就直接返回了mManager,而此时mManager还没有初始化,所以线程2中会出现空指针异常。

所以建议, 依赖一个变量来判断是否初始化,而且这种判断是运行在多线程下的,那么该变量一定要是volatile的

接下来我们看一下JVM是如何通过内存模型来保证原子性,可见性和有序性的。

JVM保证对于变量的读取和直接赋值都是原子性的,什么是直接赋值呢,比如x = 1这就是直接赋值,JVM保证其原子性。但是y = x就不是原子性的,因为这涉及到三个操作,第一步是把x的值读入工作内存,第二步是把x的值赋给y,第三步是把y的值刷回主内存。
同理类似于i++,x=x+1都不是原子性操作。

如果要确保这些操作的原子性,都要使用synchronized关键字。

JVM通过synchronized关键字,volatile关键字和显示锁Lock来保证可见性。

JVM通过synchronized关键字,volatile关键字和显示锁Lock来保证有序性。

可见volatile关键字只能保证可见性和有序性。而使用锁的话则三个特性都能保证。

volatile的使用场景

由于volatile关键字可以保证可见性和有序性,并不能保证原子性,所以它不能完全替代锁。我们工作中对volatile的使用也是利用了它的可见性和有序性。

  1. 修饰开关变量。比如我们上面提到的例子
  2. 修饰单例变量,防止在单例模式中由于指令重排生成多个变量。

volatile关键字不会导致阻塞,但是使用锁会导致阻塞

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

推荐阅读更多精彩内容