volatile实现原理

104 阅读3分钟

在Java内存模型一节,除了synchronized外,我们还提到一个常用关键词----volatile,我们说过volatile保证了并发环境的可见性和顺序性,使用volatile修饰的变量,当然值发生改变时,可以同步到其他线程,其他线程按新值进行计算,下面我们编写代码来看下volatile关键词的使用和实现原理,验证我们前面抛出的结论。

当我们使用多线程对共享变量进行操作时,如果不用volatile,会是什么现象呢?以线程一节描述的使用标识位停止线程执行为例说明,代码如下:

 public class VolatileTest {
     // 标志位
     boolean mInterrupted = false;
   
     // 设置标志位为true,打断线程运行
     public void needInterrupt(boolean isNeedInterrupt) {
         this.mInterrupted = isNeedInterrupt;
         System.out.println("修改mInterrupted值为:" + mInterrupted + ",time:" + System.currentTimeMillis());
     }
 ​
     public static void main(String[] args) {
         VolatileTest test = new VolatileTest();
         new Thread(() -> {
             System.out.println(Thread.currentThread().getName() + "开始执行,time:" + System.currentTimeMillis());
             while (!test.mInterrupted) {
             }
             System.out.println(Thread.currentThread().getName() + "结束执行,time:" + System.currentTimeMillis());
         }, "CustomThread1").start();
 ​
         new Thread(() -> {
             try {
                 Thread.sleep(2000);
                 test.needInterrupt(true);
                 System.out.println(Thread.currentThread().getName() + "打断关联的线程执行,time:" + System.currentTimeMillis());
             } catch (InterruptedException e) {
             }
         }, "CustomThread2").start();
     }
 }

随后运行,输出如下:

1-4-11-1

可以看出在设置标志位为true后,很长一段时间仍未打印CustomThread1停止运行的日志,换而言之,我们虽然在CustomThread2中修改了mInterrupted的值为true,但是CustomThread1中值仍然为false,明显不符合我们预期,那么加上volatile修饰又会怎样呢?修改mInterrupted声明,添加volatile修饰,运行结果如下:

1-4-11-2

可以看出在使用volatile修饰后,在CustomThread2中执行打断后,CustomThread1立即停止运行了。

volatile实现原理

在synchronized实现原理中,我们了解到可以从字节码和汇编码两个角度去了解synchronized的实现,对于volatile,我们不妨也做相同假设,首先我们获取VolatileTest的字节码,看是否有相关实现,字节码代码如下:

1-4-11-3

从字节码可以看出,针对mInterrupted变量而言,除了声明时多了一个ACC_VOLATILE标记,在使用过程中无明显变化。

看来字节码中并没有volatile的核心实现,我们继续使用hsdis获取VolatileTest类的汇编码,进一步确定其实现,字节码如下:

1-4-11-4

可以看到在更新mInterrupted值时,增加了lock addl $0x0,(%rsp)指令,该指令就使得当前对共享变量的修改对其他线程立即生效,而lock addl是内核中的写屏障指令,所以我们一般说volatile关键词底层是通过内存屏障指令实现的。在有volatile修饰的变量需要赋值时,处理器会插入内存屏障指令,将当前线程工作内存的值刷新进主存,同时使得其他线程工作内存中引用的该变量的缓存地址失效,重新从主存同步新值,完成业务逻辑。

volatile与synchronized

volatile与synchronized区别见下表:

关键字并发特性作用级别是否阻塞其他线程字节码实现汇编实现备注
volatile顺序性,可见性变量/lock addl
synchronized顺序性,可见性,原子性变量,函数monitorenter/monitorexit/ACC_SYNCHRONIZEDlock cmpxchg