范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

深入理解关键字volatile

  volatile 关键字的作用是什么?
  相比于 synchronized 关键字(重量级锁)对性能影响较大,Java提供了一种较为轻量级的可见性和有序性问题的解决方案,那就是使用 volatile 关键字。由于使用 volatile 不会引起上下文的切换和调度,所以 volatile 对性能的影响较小,开销较低。
  从并发三要素的角度看,volatile 可以保证其修饰的变量的可见性和有序性,无法保证原子性(不能保证完全的原子性,只能保证单次读/写操作具有原子性,即无法保证复合操作的原子性)。
  下面将从并发三要素的角度介绍 volatile 如何做到可见和有序的。1. volatile 如何实现可见性?
  什么是可见性?
  可见性指当多个线程同时访问共享变量时,一个线程对共享变量的修改,其他线程可以立即看到(即任意线程对共享变量操作时,变量一旦改变所有线程立即可以看到)。1.1 可见性例子/**  * volatile 可见性例子  * @author 单程车票  */ public class VisibilityDemo {      // 构造共享变量     public static boolean flag = true; //    public static volatile boolean flag = true;   // 如果使用volatile修饰则可以终止循环      public static void main(String[] args) {         // 线程1更改flag         new Thread(() -> {             // 睡眠3秒确保线程2启动             try { TimeUnit.SECONDS.sleep(3);  } catch (InterruptedException e) {e.printStackTrace();}             // 修改共享变量             flag = false;             System.out.println("修改成功,当前flag为true");         }, "one").start();          // 线程2获取更新后的flag终止循环         new Thread(() -> {             while (flag) {              }             System.out.println("获取到修改后的flag,终止循环");         }, "two").start();     } }不使用 volatile 修饰 flag 变量时,运行程序会进入死循环,也就是说线程1对 flag 的修改并没有被线程2读到,也就是说这里的flag并不具备可见性。使用 volatile 修饰 flag 变量时,运行程序会终止循环,打印提示语句,说明线程2读到了线程1修改后的数据,也就是说被 volatile 修饰的变量具备可见性。1.2 volatile 如何保证可见性?
  被 volatile 修饰的共享变量 flag 被一个线程修改后,JMM(Java内存模型)会把该线程的CPU内存中的共享变量 flag 立即强制刷新回主存中,并且让其他线程的CPU内存中的共享变量 flag 缓存失效,这样当其他线程需要访问该共享变量 flag 时,就会从主存获取最新的数据。
  所以通过 volatile 修饰的变量可以保证可见性。
  两点疑问及解答:为什么会有CPU内存? 为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1/L2/其他)后再进行操作,但是操作完后的数据不知道何时才会写回主存。所以如果是普通变量(未被修饰的),什么时候被写入主存是不确定的,所以读取的可能还是旧值,因此无法保证可见性。各个线程的CPU内存是怎么保持一致性的? 实现了缓存一致性协议(MESI),MESI在硬件上约定了:每个处理器通过嗅探在总线上传播的数据来检查自己的CPU内存的值是否过期,当处理器发现自己的缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置为无效状态。当处理器对该数据进行修改操作时,会重新从系统内存(主存)中把数据读到处理器缓存(CPU内存)里。1.3 volatile 实现可见性的原理
  原理一:Lock指令(汇编指令)
  通过上面的例子的Class文件查看汇编指令时,会发现变量有无被 volatile 修饰的区别在于被 volatile 修饰的变量会多一个lock前缀的指令。
  lock前缀的指令会触发两个事件:将当前线程的处理器缓存行(CPU内存的最小存储单元,这里可以大致理解为CPU内存)的数据写回到主存(系统内存)中写回主存的操作会使其他线程的CPU内存中该内存地址的数据无效(缓存失效)
  所以使用 volatile 修饰的变量在汇编指令中会有lock前缀的指令,所以会将处理器缓存的数据写回主存中,同时使其他线程的处理器缓存的数据失效,这样其他线程需要使用数据时,会从主存中读取最新的数据,从而实现可见性。
  原理二:内存屏障(CPU指令)
  volatile的可见性实现除了依靠上述的LOCK指令(汇编指令)还依靠内存屏障(CPU指令)。
  为了性能优化,JMM 在不改变正确语义的前提下,会允许编译器和处理器对指令序列进行重排序。JMM 提供了内存屏障阻止这种重排序。
  这里介绍的是内存屏障中的一类:读写屏障(用于强制读取或刷新主存的数据,保证数据一致性)Store屏障:当一个线程修改了volatile变量的值,它会在修改后插入一个写屏障,告诉处理器在写屏障之前将所有存储在缓存中的数据同步到主内存。Load屏障:当另一个线程读取volatile变量的值,它会在读取前插入一个读屏障,告诉处理器在读屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果。
  对上面的例子使用javap查看JVM指令时,如果被 volatile 修饰时多一个 ACC_VOLATILE,JVM把字节码生成机器码时会在相应位置插入内存屏障指令,因此可以通过读写屏障实现 volatile 修饰变量的可见性。
  注意读写屏障的特点:可以将所有变量(包括不被 volatile 修饰的变量)一起全部刷入主存,尽管这个特性可以使未被 volatile 修饰的变量也具备所谓的可见性,但是不应该过于依赖这个特性,在编程时,对需要要求可见性的变量应当明确的用 volatile 修饰(当然除了volatile,synchronized、final以及各种锁都可以实现可见性,这里不过多说明)。2. volatile 如何实现有序性?
  有序性是什么?
  有序性指禁止指令重排序,即保证程序执行代码的顺序与编写程序的顺序一致(程序执行顺序按照代码的先后顺序执行)。
  为什么会发生指令重排序?
  现代计算机为了能让指令的执行尽可能的同时运行起来,采用指令流水线的方式,若指令之间不具有依赖,可以使流水线的并行最大化,所以CPU对无依赖的指令可以乱序执行,这样可以提高流水线的运行效率,在不影响最后结果的情况下,Java编译器可以通过指令重排序来优化性能。
  编译器和处理器常常会对指令做重排序,一般分为三种类型:编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。指令级并行重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。内存系统重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
  所以指令重排序是指编译器和处理器为了优化程序的性能,在不改变数据依赖性的情况下,调整指令的执行顺序。
  这种优化在单线程情况下没有问题,但是在多线程情况下可能会导致影响程序结果。接下来将介绍一个多线程下指令重排的例子。2.1 有序性例子
  这里以单例模式的常用实现方式 DLC双重检查 为例子/**  * volatile 有序性例子  * @author 单程车票  */ public class Singleton {          // 使用volatile进行修饰     private static volatile Singleton instance;      // 私有化构造器     private Singleton() {}          // 双重检查锁     public static Singleton getInstance() {         if (instance == null){             synchronized (Singleton.class){                 if (instance == null){                     instance = new Singleton();                 }             }         }         return instance;     } }
  如果写过单例模式的双重锁检查实现方式,会发现声明的变量被volatile修饰,那么为什么这里需要使用volatile修饰呢?
  第一个原因是可见性,如果没有 volatile 修饰的话,当一个线程给 instance 赋值即instance = new Singleton();后,其他线程如果无法及时看到 instance 更新,会导致创建多个单例对象,这样就不符合单例模式设计思想了,所以需要使用 volatile 修饰。
  第二个原因则是禁止指令重排序(保证有序性),为什么需要禁止指令重排呢?
  首先需要了解实例一个对象可以分为三个步骤:分配内存空间初始化对象将对象引用赋值给变量
  由于指令可以进行重排序,所以步骤可能发生变化变为分配内存空间将对象引用赋值给变量初始化对象
  如果未使用 volatile 修饰变量的话,多线程情况下可能出现这样的情况:
  一个线程在执行第二步(将对象引用赋值给变量,即此时变量不为 null )时,而另一个线程进入第一次非空检查,此时发现变量不为 null ,直接返回对象,但是此时的对象由于指令重排序的原因并未进行初始化,即返回了一个未初始化的对象。将一个未初始化的变量暴露出来会导致不可预料的后果。
  所以需要 volatile 保证变量有序性,禁止指令重排序。2.2 volatile 实现有序性的原理
  内存屏障的四种指令
  内存屏障中禁止指令重排序的内存屏障的四种指令
  指令
  说明
  LoadLoad 屏障
  保证在该屏障之后的读操作,不会被重排序到该屏障之前的读操作
  StoreStore屏障
  保证在该屏障之后的写操作,不会被重排序到该屏障之前的写操作,并且该屏障之前的写操作已被刷入主存
  StoreLoad 屏障
  保证在该屏障之后的读操作,能够看到该屏障之前的写操作对应变量的最新值
  LoadStore 屏障
  保证在该屏障之后的写操作,不会被重排序到该屏障之前的读操作
  Java编译器会在生成指令时在适当位置插入内存屏障来禁止特定类型的处理器重排序。
  volatile的插入屏障策略在每个 volatile 写操作的前面插入一个 StoreStore 屏障在每个 volatile 写操作的后面插入一个 StoreLoad 屏障在每个 volatile 读操作的后面插入一个 LoadLoad 屏障在每个 volatile 读操作的后面插入一个 LoadStore 屏障
  即在每个volatile写操作前后分别插入内存屏障,在每个volatile读操作后插入两个内存屏障。
  如何通过内存屏障保持有序性?
  分析上面的双重检查锁例子:
  不加 volatile 修饰时,多线程下可能出现的情况是这样的:
  为了避免这种情况,使用 volatile 修饰变量时,会插入内存屏障// 双重检查锁 public static Singleton getInstance() {     if (instance == null){                   // 第一次检查         synchronized (Singleton.class){      // 加锁             if (instance == null){           // 第二次检查                 插入 StorStore屏障           // 插入屏障禁止下面的new操作和读取操作重排序                 instance = new Singleton();  // 创建对象                 插入 LoadLoad屏障            // 插入屏障禁止下面的读取操作和上面的new操作重排序             }         }     }     return instance; }
  这里使用 volatile 修饰变量并不能避免实例对象的三个步骤重排序,因为 volatile 关键只能避免多个线程之间的重排序,不能避免单个线程内部的重排序。
  这里 volatile 保证有序性的作用在于插入屏障之后必须等创建对象完成后才能进行读取操作,也就是说需要线程1的创建对象整个步骤完成后才会让线程2进行读取,禁止了重排序,这样就避免了返回一个未初始化的对象,保证了有序性。3. volatile 为什么不能保证原子性?
  什么是原子性?
  原子性指一个操作或一系列操作是不可分割的,要么全部执行成功,要么全部不执行(中途不可被中断)。
  为什么volatile不能保证原子性呢?
  通过一个例子来证明volatile不能保证原子性/**  * 原子性例子  * @author 单程车票  */ public class AtomicityDemo {          // 使用volatile修饰变量     public static volatile int i = 0;      public static void main(String[] args) {         ExecutorService pool = Executors.newFixedThreadPool(1000);                  // 多线程情况下执行1000次         for (int j = 0; j < 1000; j++) {             pool.execute(() -> i++);         }                  // 打印结果         System.out.println(i);         pool.shutdown();     } }  /* 输出结果:     997 */
  正常情况下,打印结果应该为1000,但是这里却是997,说明这段程序并不是线程安全的,可以看出 volatile 无法保证原子性。
  准确来说应该是 volatile 无法保证复合操作的原子性,但能保证单个操作的原子性。
  这里 volatile 保证单个操作的原子性可以应用于 使用 volatile 修饰共享的 long 或者 double 变量(可以避免字分裂情况,具体想要了解到可以查阅相关资料这里不做过多说明)。
  i++ 操作是原子操作吗?
  i++ 其实不是原子操作,实际上 i++ 分为三个步骤:读取 i 的值将 i 自增1(i + 1)写回 i 的新值(i = i + 1)
  这三个步骤每一步都是原子操作,但是组合起来就不是原子操作了,在多线程情况下同时执行 i++,会出现数据不一致性的问题。
  所以可以证明 volatile 修饰的变量无法保证原子性。
  可以通过 AtomicInteger 或者 synchronized 来保证 i++ 的原子性。4. volatile 常见的应用场景?4.1 状态标志位
  使用 volatile 修饰一个变量通过赋值不同的常数或值来标识不同的状态。/**  * 可以通过布尔值来控制线程的启动和停止  */ public class MyThread extends Thread {          // 状态标志变量     private volatile boolean flag = true;          // 根据状态标志位来执行     public void run() {         while (flag) {             // do something         }     }     // 根据状态标志位来停止     public void stopThread() {         flag = false; // 改变状态标志变量     } }4.2 双重检查DLC
  在多线程编程下,一个对象可能会被多个线程同时访问和修改,而且这个对象可能会被重新创建或者赋值为另一个对象。此时可以通过 volatile 来修饰该变量,保证该变量的可见性和有序性。
  就如单例模式的双重检查DLC可以通过 volatile 来修饰从存储单例模式对象的变量。/**  * 单例模式的双重检查方式  */ public class Singleton {          // 使用volatile进行修饰     private static volatile Singleton instance;      // 私有化构造器     private Singleton() {}          // 双重检查锁     public static Singleton getInstance() {         if (instance == null){             synchronized (Singleton.class){                 if (instance == null){                     instance = new Singleton();                 }             }         }         return instance;     } }4.3 较低开销的读写锁
  使用 volatile 结合 synchronized 实现较低开销的读写锁,由于 volatile 可以保证变量的可见性和有序性,而 synchronized 可以保证变量的原子性和互斥性,可以结合使用实现较低开销的读写锁。/**  * 读写锁实现多线程下的计数器  */ public class VolatileSynchronizedCounter {     // volatile变量     private volatile int count = 0;     // synchronized方法     public synchronized void increment() {          count++; // 原子操作     }     public int getCount() {         return count;     } }
  使用 volatile 修饰变量,synchronized 修饰方法,这样 volatile 修饰变量具有可见性,写操作会被其他线程立刻可见,synchronized 修饰方法保证 count++ 操作的原子性和互斥性,这样实现的读写锁,读操作无锁,写操作有锁,降低了开销。

浅析唐朝服饰中各类纹样文聪聪编辑聪聪前言唐代是中国历史上的一个辉煌时期,其文化艺术科技和经济等方面都取得了令人瞩目的成就,而唐代各式纹样是唐代艺术的重要表现形式之一,本文旨在探讨唐朝各式纹样元素在设计中学雷锋,火箭军部队在行动人的生命是有限的,可是,为人民服务是无限的,我要把有限的生命,投入到无限的为人民服务之中去。雷锋今年是毛泽东等老一辈革命家为雷锋同志题词60周年火箭军部队广泛开展学雷锋系列活动引导11个月女婴喉咙取出2厘米鱼刺,医生添加辅食要注意!湖南日报2月17日讯(全媒体记者王铭俊通讯员马丹)近日,出生仅11个月的女婴琪琪在进食时突然出现呕吐及哭闹不适,随后不愿继续进食进水,妈妈怀疑在给琪琪做的辅食里有鱼刺没挑干净,担心一喝奶就吐,宝宝出生23天就上手术台!孩子吐奶,出现这种情况就要注意了!做了父母的都知道,家里的娃常常会制造各种大型灾难现场比如毫无预兆的吐奶!出生刚19天就发现娃胃出口被堵了12月12日一大早,一对父母抱着刚19天大的阳阳(化名)来到湖南妇女儿童医院孕妇为什么容易得痔疮?有什么危害!说起痔疮,相信很多人都不陌生,甚至有些人正饱受痔疮的困扰,痔疮在出现后会让患者在排便时总感觉到痛苦,甚至有些会出现大便出血的现象,在众多的的人群中,孕妇是容易长痔疮的一类人群,所以孩子有阅读障碍,做父母的应如何理解和帮助孩子?你有没有经历过一些时刻,坚强乐观被一个问题轻松击垮,比如当孩子放学回家,问你妈妈,我是不是个笨蛋?当孩子在阅读方面苦苦挣扎,甚至因为跟不上同学的进度而崩溃,被同学,老师嘲笑。那么,宝宝拔牙记依依第一颗牙齿松动的时候,当听到妈妈告知要换牙时,竟然爆发伤心至极的哭声。妈妈,我怕疼,呜呜呜舒畅和家婆听见女儿悲伤的哭声,也泛起无奈和心疼。印象中,舒畅及同龄人小时候拔牙,牙齿松孩子生长迟缓?请看这份指南图片来源国家卫生健康委官网孩子比同龄人长得慢,个子长不高,体重偏轻这些在孩子成长中遇到的问题困扰着不少家长。孩子生长迟缓,如何通过食养来改善?近日,国家卫生健康委办公厅印发儿童青少家里的健康疗愈师,是福音健康守门员七旬老人被骗养老金买保健品足浴店以排毒养生诈骗老人79万近两年有很多老年人被养生诈骗的新闻,这样的事情在生活中经常会看到,为什么会发生这种事情?原因不外乎两点第一,老人在健康方面的加盟半职业联赛不忘初心?受李铁牵连无人敢收!周通留洋OR跑了?前几天,很多细心的球迷,就发现了周通社交账号的IP定位在新西兰。现如今德转已经正式确认,他加盟了奥克兰城。再考虑到新西兰联赛转会窗是2月8日关闭的,因此双方签约恐怕已经有一段时间了35岁生日,频繁上综艺捞金,退出国乒4年的张继科赚到多少钱?2月16日,张继科迎来了自己的35岁生日,这个昔日在24岁就收获了乒乓球大满贯的少年如今也是变成了中年大叔,而且如今的张继科似乎和国乒也没有太多的关联。2018年底,张继科开始淡出
程序员8年薪资变化走红,网友羡慕不已一年和人家一个月差不多文萌妈教育日记正所谓3分考7分报,马上一年一度的填报高考志愿环节又要到来了,学生们要谨慎选择,综合考虑。高考选择的专业,基本上就是学生们未来要从事的行业,想要在日益增加的毕业生里脱王传君现身郭麒麟新综艺,外形依旧颓废萎靡,网友留言直戳痛处宋丹丹新综艺五十公里桃花坞2开播,首发的预告里,就和郭麒麟来个母子重逢,宋丹丹也毫不避讳地说道郭麒麟,你变胖了,还旁敲侧击地打听郭麒麟有没有对象?母子俩你来我往的聊天,被网友直言,习近平推动扫黑除恶常态化,让城乡更安宁群众更安乐平安,是每个人最朴素的愿望让人民群众享有一个安全稳定的生存生活环境,是中国共产党治国理政的重要目标。党的十八大以来,以习近平同志为核心的党中央高度重视平安中国建设,人民获得感幸福感上市公司在海南丨吉利汽车与三亚理工职业学院共建的吉利新能源汽车产业学院正式揭牌海财经证券导报6月15日讯(记者梁天琪)6月14日,三亚理工职业学院吉利新能源汽车产业学院正式揭牌。该院由吉利汽车(00175。HK)全资子公司吉利汽车集团有限公司与三亚理工职业学唐山打人事件9人全部落网主犯的背景并不简单根据公开报道,河北省委主要负责人已经关注到了唐山打人事件。据河北新闻联播报道,中国共产党河北省第十届委员会第二次全体会议,于2022年6月13日至14日在石家庄召开。唐山打人事件9北大教授直言未来10年,这3大职业很可能被淘汰,考生谨慎报考导语科技是把双刃剑,给我们带来更丰富,更便利生活的同时,也淘汰了许多原有的生活方式,以及不愿意随着时代发展变化的人,就连各行各业的工作人员,也在科技的发展下受到一定冲击。人生在世,如何使用Python制作Web应用程序用户界面如何使用Python构建WebUIAnvil让您可以完全用Python构建应用程序的前端无需HTMLCSS或Javascript。您可以通过在Anvil的可视化设计器中拖放组件或使2013年,四川女子与相识13天网友闪婚,以为幸福一生,却陷入阴谋前言小伙子,你帮我看看湖里漂的是什么?怎么看着像是个人呢?2013年5月10日清晨,7时许,江苏常州一名大学生周皓正在公园晨跑,突然被一个大爷叫住。顺着大爷手指的方向,周皓走到湖边华裔高一学生研究出神奇假手,专家小心被色狼利用在大流行期间,人们在闲暇时间进行了各种有趣的项目。面包烘焙园艺针织这些都是被人们所接受的常见爱好,但也有一些人的爱好远比这更伟大。弗吉尼亚州的华裔高中生本杰明崔(以下简称小崔)开始海外网友热议V5击败TTshowmaker在LPL的首秀,表现不错LPL夏季赛中,V5以20的战绩击败了TT,拿下了开门红。其中这场比赛中,V5中单Rookie因为家里的事情缺席,由2队中单Dream代替首发,而Dream因为外表酷似showma几十人围观,500一次西安女网红饭局视频流出,惹怒网友作者丨酱姨这是酱姨陪你的第444个夜晚大家好,我是你们的小贝。一个啥都能三口吃完的女子。这是西安一位网红小贝饿了,最被大家熟知的自我介绍视频开场白。在这句话的吸引之下,大家看着这位