并发编程之volatile

  • 2019 年 12 月 31 日
  • 筆記

文章目录

1. 并发编程之volatile

1.1. volatile的作用

1.2. 原理

1.2.1. 保证可见性原理

1.2.2. 保证有序性原理

1.2.3. 总结

1.3. volatile变量的开销

1.4. 使用场景

1.5. 参考文章

并发编程之volatile

  • volatile称之为轻量级锁,保证了可见性和原子性。
  • volatile不会引起上下文切换,因此是轻量级的

volatile的作用

  • 保障可见性,有序性和Longdouble类型变量读写操作的原子性
  • volatile仅仅能保证对其修饰的变量的写操作以及读操作本身的原子性,而这并不表示volatile变量的赋值操作一定具有原子性,例如,如下对volatile修饰的变量count的赋值操作并不是原子操作:count++
    • count++可以分为如下步骤
      • 读取count的值
      • count+1
      • count+1的值赋值给count
    • 如果count是一个共享变量,那个该赋值操作实际上是一个read-modify-write操作。其执行过程中其他线程可能已经更新了count的值,因此该操作不具备不可分割性,也就不是原子操作。如果变量count是一个局部变量,那么该赋值操作就是一个原子操作。
  • 一般而言如果对volatile修饰的赋值操作,其右边的表达式中只要设计共享变量(包括被volatile修饰的变量本身),那么这个赋值操作就不是原子操作,此时就需要结合来保证原子性了

原理

保证可见性原理

  • 对volatile修饰的变量的读操作之前插入一个加载屏障,能够刷新处理器缓存,使其读取的读取到的变量都是线程更新后的最新值。
  • 对volatile修饰的变量的写操作(修改)之后插入一个存储屏障,能够冲刷处理器缓存,保证后续的线程读取到的值是最新的。

保证有序性原理

  • 结合释放屏障和获取屏障保证了有序性

总结

  • volatile的写操作相当于锁的释放的效果,java虚拟机会在该操作之前插入一个释放屏障,并在该操作只有插入一个存储屏障
    • 释放屏障禁止了volatile写操作与该操作之前的任何读写操作进行重排序,从而保证了volatile写操作之前的任何读写操作会先于volatile写操作之前提交,即其他线程看到写线程对volatile变量的更新时,写线程在更新volatile变量之前执行的内存操作的结果对于度鲜橙必然是可见的。即是保障了有序性
    • 存储屏障保证了在写操作之后的更新能够冲刷处理器缓存,使得后续的读线程能够获取最新的值
  • volatile的读操作相当于获取锁的效果,Java虚拟机会在该操作之前插入一个加载屏障,并在该操作之后插入一个获取屏障
    • 加载屏障用于刷新处理器缓存区,保证读取到volatile修饰变量的最新值,保证可见性
    • 获取屏障禁止volatile读操作之后的任何读写操作与volatile读操作进行重排序。因此保证了有序性

volatile变量的开销

  1. volatile变量的读写不会导致上下文切换,因此开销比锁小
  2. 读取volatile变量每次都需要从高速缓存或者主内存中读取,而无法暂存在寄存器中,因此可能比读取普通变量的成本要高

使用场景

  1. 使用volatile变量作为状态标志。比如volatile int flag=false,其他线程会读取该状态作为执行某一个操作的依据
  2. 单例模式下的双重校验锁的实现效果,其中必须使用volatile,否则并不能保证对象可见性

参考文章