Java并发编程 Volatile关键字解析

发表于:2017-9-20 10:00

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:林小白    来源:51Testing软件测试网采编

  volatile关键字的两层语义
  一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2)禁止进行指令重排序。
  根据volatile的语义,我们可以看到,volatile主要针对的是并发三要素(原子性,可见性和有序性)中的后两者有实际优化作用。
  可见性:
  线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作。这也是导致线程间数据不可见的本质原因。因此要实现volatile变量的可见性,直接从这方面入手即可。对volatile变量的写操作与普通变量的主要区别有两点:
  (1)修改volatile变量时会强制将修改后的值刷新的主内存中。
  (2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。
  通过这两个操作,就可以解决volatile变量的可见性问题。
  有序性:
  volatile会触发jvm的内存屏障策略
  内存屏障策略:
  (1)LoadLoad屏障
  执行顺序:Load1—>Loadload—>Load2
  确保Load2及后续Load指令加载数据之前能访问到Load1加载的数据。
  (2)StoreStore屏障
  执行顺序:Store1—>StoreStore—>Store2
  确保Store2以及后续Store指令执行前,Store1操作的数据对其它处理器可见。
  (3)LoadStore屏障
  执行顺序:Load1—>LoadStore—>Store2
  确保Store2和后续Store指令执行前,可以访问到Load1加载的数据。
  (4)StoreLoad屏障
  执行顺序:Store1—>StoreLoad—>Load2
  每次对volatile进行读写操作,根据上述表格,会触发对应的CPU指令,从线程内存缓冲区将之前更改的变量刷入主存。
  简单来说,volatile会在一定程度上影响jvm指令集的优化策略,在volatile之前和之后的指令集不会乱序越过volatile变量执行。暂时volatile之前和之后的指令集在没有关联性的前提下,jvm可以乱序执行。
  jvm的volatile策略,在一定程度上,打折扣地实现了jvm的happens-before原则(先行发生原则),如下所述。
  ●程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  ●锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
  ●volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  ●传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  ●线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
  ●线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  ●线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  ●对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
  volatile常见使用场景:
  (1)状态标记量
  (2)单例模式一次性安全发布
  (3)低开销读写锁
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号