学会了volatile,你变心了,我看到了

更多精彩文章,请关注xhJaver,京东工程师和你一起成长

volatile 简介

一般用来修饰共享变量,保证可见性和可以禁止指令重排

  • 多线程操作同一个变量的时候,某一个线程修改完,其他线程可以立即看到修改的值,保证了共享变量的可见性

  • 禁止指令重排,保证了代码执行的有序性

  • 不保证原子性,例如常见的i++

    (但是对单次读或者写保证原子性)

可见性代码示例

以下代码建议使用PC端来查看,复制黏贴直接运行,都有详细注释

我们来写个代码测试一下,多线程修改共享变量时究竟需不需要用volatile修饰变量

  1. 首先,我们创建一个任务类
public class Task implements Runnable{
   @Override
   public void run() {
       System.out.println("这是"+Thread.currentThread().getName()+"线程开始,flag是 "+Demo.flag);
       //当共享变量是true时,就一直卡在这里,不输出下面那句话
       // 当flag是false时,输出下面这句话
       while (Demo.flag){

       }
       System.out.println("这是"+Thread.currentThread().getName()+"线程结束,flag是 "+Demo.flag);
   }
}

2.其次,我们创建个测试类

class Demo {

   //共享变量,还没用volatile修饰
   public static   boolean flag = true ;
   public static void main(String[] args) throws InterruptedException {
       System.out.println("这是"+Thread.currentThread().getName()+"线程开始,flag是 "+flag);
       //开启刚才线程
       new Thread(new Task()).start();
       try {
           //沉睡一秒,确保刚才的线程已经跑到了while循环
           //要不然还没跑到while循环,主线程就将flag变为false
           Thread.sleep(1000L);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       //改变共享变量flag转为false
       flag = false;
       System.out.println("这是"+Thread.currentThread().getName()+"线程结束,flag是 "+flag);
   }
}

3.我们查看一下输出结果

无volatile修饰.jpg

可见,程序并没有结束,他卡在了这里,为什么卡在了这里呢,就是因为我们在主线程修改了共享变量flag为false,但是另一个线程没有感知到,这个变量的修改对另一个线程不可见

  • 如果要是用volatile变量修饰的话,结果就变成了下面这个样子

public static volatile boolean flag = true

有volatile修饰.jpg

可见,这次主线程修改的变量被另一个线程所感知到了,保证了变量的可见性

可见性原理分析

那么,神奇的 volatile 底层到底做了什么呢,你的改变,逃不过他的法眼?为什么不用他修饰变量的话,变量的改变其他线程就看不见?

回答此问题的时候首先,我们需要了解一下JMM(Java内存模型)

JMM.jpg

注:本地内存是JMM的一种抽象,并不是真实存在的,本地内存它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化之后的一个数据存放位置

  • 由此我们可以分析出来,主线程修改了变量,但是其他线程不知道,有两种情况

    1. 主线程修改的变量还没有来得及刷新到主内存中,另一个线程读取的还是以前的变量
    2. 主线程修改的变量刷新到了主内存中,但是其他线程读取的还是本地的副本
  • 当我们用 volatile 关键字修饰共享变量时就可以做到以下两点

    1. 当线程修改变量时,会强制刷新到主内存中
    2. 当线程读取变量时,会强制从主内存读取变量并且刷新到工作内存中

指令重排

  • 何为指令重排?

为了提高程序运行效率,编译器和cpu会对代码执行的顺序进行重排列,可这有时候会带来很多问题

我们来看下代码

//指令重排测试
public class Demo2 {

   private Integer number = 10;
   private boolean flag = false;
   private Integer result = 0;

   public void  write(){
       this.flag = true; // L1
       this.number = 20; // L2
   }

   public void  reader(){
        while (this.flag){ // L3
            this.result = this.number + 1; // L4
        }
   }
}

假如说我们有A、B两个线程 他们分别执行write()方法和 reader()方法,执行的顺序有可能如下图所示


重排序.jpg
  • 问题分析: 如图可见,A线程的L2和L1的执行顺序重排序了,如果要是这样执行的话,当A执行完L2时,B开始执行L3,可是这个时候flag还是为false,那么L4就执行不了了,所以result的值还是初始值0,没有被改变为21,导致程序执行错误

这个时候,我们就可以用volatile关键字来解决这个问题,很简单,只需

private volatile Integer number = 10;

  • 这个时候L1就一定在L2前面执行

A线程在修改number变量为20的时候,就确保这句代码的前面的代码一定在此行代码之前执行,在number处插入了内存屏障 ,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排

内存屏障

内存屏障又是什么呢?一共有四种内存屏障类型,他们分别是

  1. LoadLoad屏障:

    • Load1 LoadLoad Load2 确保Load1的数据的装载先于Load2及所有后续装载指令的装载
  2. LoadStore屏障:

    • Load1 LoadStore Store2 确保Load1的数据的装载先于Store2及所有后续存储指令的存储
  3. StoreLoad屏障:

    • Store1 StoreLoad Load2 确保Store1的数据对其他处理器可见(刷新到内存)先于Load2及所有后续的装载指令的装载
  4. StoreStore屏障:

    • Store1 StoreStore Store2 确保Store1数据对其他处理器可见(刷新到内存)先于Store2及所有后续存储指令的存储

    StoreLoad 是一个全能型的屏障,同时具有其他3个屏障的效果。执行该屏障的花销比较昂贵,因为处理器通常要把当前的写缓冲区的内容全部刷新到内存中(Buffer Fully Flush)

  • 装载load 就是读 int a = load1 ( load1的装载)
  • 存储store就是写 store1 = 5 ( store1的存储)

volatile与内存屏障

那么volatile和这四种内存屏障又有什么关系呢,具体是怎么插入的呢?

  1. volatile写 (前后都插入屏障)

    • 前面插入一个StoreStore屏障
    • 后面插入一个StoreLoad屏障


      volatile写屏障.jpg
  2. volatile读(只在后面插入屏障)

    • 后面插入一个LoadLoad屏障
    • 后面插入一个LoadStore屏障


      volatile读内存屏障.jpg

官方提供的表格是这样的


内存屏障表格.jpg

我们此时回过头来在看我们的那个程序

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">this.flag = true; // L1 this.number = 20; // L2 </pre>

由于number被volatile修饰了,L2这句话是volatile写,那么加入屏障后就应该是这个样子

this.flag = true; // L1
 //  StoreStore  确保flag数据对其他处理器可见(刷新到内存)先于number及所有后续存储指令的存储
 this.number = 20; // L2
 // StoreLoad  确保number数据对其他处理器可见(刷新到内存)先于所有后续存储指令的装载

所以L1,L2的执行顺序不被重排序

更多精彩,请关注xhJaver,京东工程师和你一起成长

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

推荐阅读更多精彩内容

  • 在实际的工作中,可能我们比较少去使用的一个关键字就是volatile,但是观察源码的时候却是经常遇到。如果不搞懂何...
    瑜小贤阅读 677评论 0 1
  • 一旦一个共享变量(类的成员变量、 类的静态成员变量) 被 volatile 修饰之后, 那么就具备了两层语义: 保...
    六尺帐篷阅读 1,000评论 0 9
  • 转自:https://juejin.im/editor/drafts/5acda6976fb9a028d93782...
    简步超阅读 137评论 0 0
  • 加州大学圣地亚哥分校(美国)校训:“愿知识之光普照大地。” 夏季,收获的季节,可看着A股大盘一直趴着,又是徒劳了半...
    阿伦故事2019阅读 376评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,517评论 16 22