reference

https://www.huaweicloud.com/articles/13402092.html

是什么

volatile是java虚拟机提供的最轻量级的同步机制

特性

  • 保证此变量对所有线程的可见性
  • 禁止指令重排序优化
    • 指令重排序优化(With-Thread As-If-Serial-Semantics)
      • 普通变量仅仅保证在执行过程中依赖赋值结果的地方能够拿到正确的结果,不能保证变量赋值的顺序和程序代码中的执行顺序一致
      • 一个线程在执行过程中无法感知指令可能重排序优化过

volatile禁止了指令重排序?

  • 有volatile修饰的变量,赋值后多执行了内存屏障操作。即在本地代码中插入字节码指令
  1. //关键在于lock前缀,使得本cpu的cache写入内存,
  2. //该写入动作也会引起别的cpu或者别的内核invalidate cache
  3. //addl $0x0, (%esp)是个空操作
  4. lock addl $0x0, (%esp)
  • 通过内存屏障,指重排序时不能把后面的指令重排序到内存屏障之前的位置
    • 从硬件架构上讲,指令重排序指cpu采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理
    • lock addl $0x0, (%esp) 把修改同步到内存,意味着数据已经保存,也就意味着之前的操作都执行完成

内存屏障保持一致性

  • 只有一个cpu访问内存时并不需要内存屏障
  • 两个或者更多cpu访问同一块内存,并且有一个在观测另一个就需要内存屏障来保证一致性

指令重排序可能导致的问题

  • 线程a执行完任务后改变变量flag
  • 线程b根据变量flag的值变化而做其他工作
  • 线程a由于指令重排序可能导致先改变了变量flag再跑任务

    线程b本意是等待a执行了任务之后才做其他工作,指令重排序导致违背了本意

线程安全

volatile变量只保证可见性,不符合以下规则仍然要使用其他手段保证原子性(synchronized
或者原子类):

  • 运算结果不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束
  • 如果是多个线程同时修改某个值譬如++操作等,需要增加锁来保证原子性

volatile使用场景1

用volatile来控制并发

  1. class CurrentControl {
  2. @Volatile private var shutdownRequested: Boolean = false
  3. fun shutdown() {
  4. shutdownRequested = true
  5. }
  6. //
  7. fun doWork() {
  8. while (!shutdownRequested) {
  9. //do something
  10. }
  11. }
  12. }

volatile使用场景2

DCL单例

  1. public class DoubleCheckedLocking {
  2. //屏蔽指令重排序的语义在JDK1.5中才被完全修复,此前的JDK中即使将变量声明为volatile也仍然不能完全避免重排序所导致的问题
  3. // (主要是volatile变量前后的代码仍然存在重排序问题),这点也是在JDK1.5之前的Java中无法安全地使用DCL(双锁检测)的原因
  4. private volatile static DoubleCheckedLocking instance;
  5. /**
  6. * double check Lock实现单例,比上一个方法好,但是还是有缺陷(在JDK 1.5之前有DLC失效问题)
  7. * 不赞成使用. 其实我觉得可以使用,因为现在android等所有开发环境都是jdk1.7或者以上没有这个问题
  8. * @return
  9. */
  10. public static DoubleCheckedLocking getInstance2() {
  11. if (instance == null) {
  12. synchronized (DoubleCheckedLocking.class) {
  13. if (instance == null) {
  14. instance = new DoubleCheckedLocking();
  15. }
  16. }
  17. }
  18. return instance;
  19. }
  20. }

volatile vs 锁

  • volatile 和普通变量性能消耗几乎没有什么差别
    • 写操作可能会慢些,因为需要插入内存屏障指令
  • volatile在大多数情况下比锁低
  • volatile与锁中选择的唯一判断依据仅仅是volatile的语义能否满足使用场景的需求