Volatile关键字

volatile 是Java虚拟机提供的 轻量级 的同步机制.何为 轻量级 呢,这要相对于 synchronized 来说。Volatile有如下三个特点。

要搞清楚上面列举的名词 可见性 原子性 指令重排 的含义我们需要首先弄清楚JMM(Java内存模型是怎么回事)

JMM规定了内存主要划分为 主内存 和 工作内存 两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存.

JVM在设计时候考虑到,如果JAVA线程每次读取和写入变量都直接操作主内存,对性能影响比较大,所以每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份 拷贝 ,线程对变量的读取和写入,直接在工作内存中操作,而不能直接去操作主内存中的变量。但是这样就会出现一个问题,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。因为JMM制定了一套标准来保证开发者在编写多线程程序的时候,能够控制什么时候内存会被同步给其他线程。

各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后再写回主内存中的。

这就可能存在一个线程A修改了共享变量X的值但还未写回主内存时,另一个线程B又对准内存中同一个共享变量X进行操作,但此时A线程工作内存中共享变量X对线程B来说并不是可见,这种工作内存与主内存同步存在延迟现象就造成了可见性问题。

通过代码来看下可见性的问题

package com.dpb.spring.aop.demo;import java.util.concurrent.TimeUnit;/** * 可见性问题分析 */public class VolatileDemo1 {    public static void main(String[] args){        final MyData myData = new MyData();        // 开启一个新的线程        new Thread(()->{            System.out.println(Thread.currentThread().getName() + "开始了...");            try{TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}            // 在子线程中修改了变量的信息  修改的本线程在工作内存中的数据            myData.addTo60();            System.out.println(Thread.currentThread().getName() + "更新后的数据是:"+myData.number);        },"BBB").start();        // 因为在其他线程中修改的信息主线程的工作内存中的数据并没有改变所以此时number还是为0        while(myData.number == 0){            // 会一直卡在此处            //System.out.println("1111");        }        System.out.println(Thread.currentThread().getName()+" number =  " + myData.number);    }}class MyData{// 没有用volatile来修饰    int number = 0;    public void addTo60(){        this.number = 60;    }}

效果如下:

通过 volatile 来解决此问题

我们可以发现当变量被 volatile 修饰的时候,在子线程的工作内存中的变量被修改后其他线程中对应的变量是可以立马知道的。这就是我们讲的可见性

原子性是 不可分割 , 完整性 ,也即某个线程正在做某个具体业务时,中间不可以被加塞或者分割,需要整体完成,要么同时成功,要么同时失败.

volatile是 不支持 原子性的,接下来我们可以验证下。

package com.dpb.spring.aop.demo;import java.util.concurrent.TimeUnit;/** * 可见性问题分析 */public class VolatileDemo2 {    public static void main(String[] args){        final MyData2 myData = new MyData2();        for (int i = 1; i <= 20 ; i++) {            new Thread(()->{                for (int j = 1; j <= 1000 ; j++) {                    myData.addPlusPlus();                }            },String.valueOf(i)).start();        }        // 等待子线程执行完成        while(Thread.activeCount() > 2){            Thread.yield();        }        // 在主线程中获取统计的信息值        System.out.println(Thread.currentThread().getName()+" finnally number value: "+myData.number);    }}class MyData2{   // 操作的变量被volatile修饰了    volatile int number = 0;    public void addPlusPlus(){        number++;    }}

执行的效果

根据正常的逻辑在开启的20个子线程,每个执行1000遍累加,得到的结果应该是20000,但是我们发现运行的结果大概率会比我们期望的要小,而且变量也已经被volatile修饰了。说明并没有满足我们要求的原子性。这种情况下我们要保证操作的原子性,我们有两个选择

  1. 通过synchronized来实现
  2. 通过 JUC 下的 AtomicInteger 来实现

synchronized的实现是重量级的,影响并发的效率,所以我们通过AtomicInteger来实现。

package com.dpb.spring.aop.demo;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;/** * 可见性问题分析 */public class VolatileDemo2 {    public static void main(String[] args){        final MyData2 myData = new MyData2();        for (int i = 1; i <= 20 ; i++) {            new Thread(()->{                for (int j = 1; j <= 1000 ; j++) {                    myData.addPlusPlus();                    myData.addAtomicPlus();                }            },String.valueOf(i)).start();        }        // 等待子线程执行完成        while(Thread.activeCount() > 2){            Thread.yield();        }        // 在主线程中获取统计的信息值        System.out.println(Thread.currentThread().getName()+" finnally number value: "+myData.number);        System.out.println(Thread.currentThread().getName()+" finnally number value: "+myData.atomicInteger.get());    }}class MyData2{   // 操作的变量被volatile修饰了    volatile int number = 0;    // AtomicInteger 来保证操作的原子性    AtomicInteger atomicInteger = new AtomicInteger();    public  void addPlusPlus(){        number++;    }    public void addAtomicPlus(){        atomicInteger.getAndIncrement();    }}

效果:

注意 :通过效果发现 AtomicInteger 在多线程环境下处理的数据和我们期望的结果是一致的都是 20000 .说明实现的操作的原子性。

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分以下3种:

数据依赖性

案例代码

package com.dpb.spring.aop.demo;public class SortDemo {    int a = 0;    boolean flag = false;    public void fun1(){        a = 1;  // 语句1        flag = true; // 语句2    }    public void fun2(){        if(flag){            a = a + 5; // 语句3            System.out.println("a = " + a );        }    }}

注意: 在多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

指令重排小结:

volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。

先了解一个概念, 内存屏障 又称 内存栅栏 ,是一个CPU指令,它的作用有两个:

  1. 是保证特定操作的执行顺序
  2. 是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重新排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

线程安全的总结:

  1. 工作内存和主内存同步延迟现象导致的 可见性问题 ,可以使用synchronized或volatile关键字解决,他们都可以使一个线程修改后的变量立即对其他线程可见。
  2. 对于指令重排导致的 可见性问题 和 有序性问题 ,可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。

volatile指令重排_有多少人面试栽到Volatile上?面试问题都总结到这儿了相关推荐

  1. volatile指令重排_面试:为了进阿里,重新翻阅了Volatile与Synchro

    面试:为了进阿里,重新翻阅了Volatile与Synchronized 在深入理解使用Volatile与Synchronized时,应该先理解明白Java内存模型 (Java Memory Model ...

  2. volatile指令重排_学会了volatile,你变心了,我看到了

    volatile 简介 一般用来修饰共享变量,保证可见性和可以禁止指令重排 多线程操作同一个变量的时候,某一个线程修改完,其他线程可以立即看到修改的值,保证了共享变量的可见性 禁止指令重排,保证了代码 ...

  3. 三维空间长度温度数量_我就随便问问:有多少人知道三维在物理上指的不是长宽高?...

    [楼主]2014-01-13 08:22 » 我就随便问问:有多少人知道三维在物理上指的不是长宽高? 我也是前几天才知道的,三维指的是长度,温度,数量 物理中的四维是指长度.数量.温度.时间,由牛顿总 ...

  4. volatile指令重排_volatile可见性和指令重排

    volatile关键字的2个作用 1.线程的可见性 2.防止指令重排 什么是线程的可见性? 线程的可见性 就是一个线程对一个变量进行更改操作 其他线程获取会获得最新的值. 线程在执行的行 操作主线程的 ...

  5. python面试大全 萧井陌_有多少人按@萧井陌大神给出的Python+Flask路线找到工作了?...

    python+Flask(之后转Django)的答一发. 背景先上,30+岁,零基础,自学.不管是年龄还是工作背景还是教育经历都同web开发无关.也没有名校背景. 二月初开始学,五月拿到offer.三 ...

  6. 口琴膜片什么作用_有多少人用无膜半音阶口琴,对于初学者来说?

    关于无膜半音阶 其实在半音阶口琴发明之初,半音阶口琴是没有膜片的.后来人们发现,那样的半音阶口琴漏气非常严重.吹气时气流会从吸气簧片溜走,还会产生杂音.后来有人使用膜片巧妙地解决的这个问题. 由膜片的 ...

  7. 学会拒绝别人的6个技巧_多少人败在不懂拒绝上!牢记10个高情商拒绝技巧,人生越来越顺...

    人际交往中,往往需要拒绝别人的请求,否定别人的意见,这并非易事,需要极高的情商,做到既能成功拒绝,又不得罪他人.否则,不懂拒绝的话,就会给自己带来许多麻烦,也会给别人带来伤害,现实生活中,有很多人,败 ...

  8. 闺蜜带对象一起在群里群名_吉珠人最新微信群名曝光!这都是什么魔鬼群聊,最后一个让我笑出哭声!...

    赚到钱很开心 就像幼儿园的小朋友一样 要把糖果分享给好朋友 一个宿舍四口人都是憨憨 幸好还有你们 为憨憨开门带早餐 哦 忘了问 明天吃什么? 真真要生日啦 嘘!我们三个背着她悄悄建了一个群~ 四六级快 ...

  9. 环境在c盘_如何给女朋友解释为什么 Windows 上面的软件都把自己安装在 C 盘

    本文经授权转载自漫画编程(ID:mhcoding) 周末,我在家里面看电视,女朋友正在旁边鼓捣她的电脑,但是好像并不是很顺利,于是就有了以下对话. 计算机存储 我们使用的计算机中,保存信息的介质有两类 ...

最新文章

  1. adb.exe: more than one device/emulator
  2. (深入理解)model.eval() 、model.train()以及torch.no_grad() 的区别
  3. systemparametersinfo详细
  4. 【实例】销售合同VA41屏幕字段增强实例
  5. LeetCode 532. 数组中的K-diff数对
  6. vue+vant 移动端H5 商城项目_01
  7. 输入输出挂,手动扩栈。
  8. 向数据库插入数据时出现乱码 --设置连接数据库的编码
  9. 合并两个LMDB文件
  10. SQL:postgresql中为查询结果增加一个自增序列之ROW_NUMBER () OVER ()的使用
  11. 线性同余法随机数生成
  12. Linux虚拟网络设备之bridge(桥)
  13. MATLAB2016笔记(七):数据分析
  14. LA4043 KM算法
  15. 音视频开发 人脸标定 animoji 动态贴纸 小项目练习总结
  16. xp访问linux系统,Linux和XP利用Samba服务实现共享互相访问
  17. 明天就是七夕了,用Python做了个可能会被女朋友打死的礼物!
  18. 一款彩票app的制作运营详解
  19. “泰迪杯” 挑战赛 - 基于市场资金流向分析的商品期货量化交易策略(附suibian代码)
  20. 倍频程频谱_倍频程科学编程语言速成课程

热门文章

  1. 【ValueError: Target is multiclass but average=‘binary‘. Please choose another average setting, one 】
  2. 源码安装NASM,无root权限
  3. C++11系列学习之五-------decltype
  4. python 加密解密_python加密解密
  5. 发现在创建云服务器ecs实例的磁盘快照时_玩转ECS第7讲|ECS数据保护-数据备份新特性与最佳实践...
  6. python函数和模块有什么特性_python-函数包和模块
  7. POJ 1150 The Last Non-zero Digit 数论+容斥
  8. 选择排序法对数组进行排序
  9. css新奇技术及其未来发展
  10. 【分享】Web前端开发第三方插件大全