当前位置: 首页 > 科技观察

深入理解Java的Volatile关键字

时间:2023-03-23 10:01:15 科技观察

前言在Java并发编程中,volatile关键字起着至关重要的作用,往往是面试中必问的问题。本文将介绍volatile关键字的作用及其实现原理。volatile的作用volatile在并发编程中扮演着重要的角色。volatile是轻量级同步的。volatile关键字有两个作用:1)保证共享变量的可见性可见性是指当一个线程修改共享变量时,另一个线程可以读取到修改后的值。笔者之前的文章《Java并发编程:Java内存模型JMM》中提到,Java内存模型中有主内存和局部内存。本地内存持有一份共享变量的副本,线程对共享变量的修改是先修改本地内存的副本,然后再写回主内存。可能会出现这样的情况,线程A和线程B同时修改了一个共享变量C。假设线程A先修改了共享变量C,但是此时线程B没有及时感知到共享变量C发生了变化。紧接着,B修改了本地过期的拷贝数据,导致共享变量不可见的问题。而volatile关键字修饰的共享变量在线程修改共享变量后会立即刷新到主存中,并会使其他线程缓存在该地址的数据失效,保证了线程间共享变量的完整性。能见度。2)防止指令重排序volatile关键字的另一个作用是防止指令重排序。在实际执行过程中,代码并不是全部按照编写的顺序执行。在保证单线程执行结果不变的情况下,编译器或CPU可能会对指令进行重新排序,以提高程序的执行效率。但是,在多线程的情况下,指令重排序可能会带来一些问题。最常见的是doublechecklock单例模式:();}}}returnssingleton;}}如果不使用volatile关键字,其他线程可能会获取到一个未初始化的单例对象,具体原因这里不再赘述。有兴趣的同学可以搜索“doublecheckedlockingwithdelayinitialization”进行研究,以后有时间我会写一篇文章来分析。volatile的实现原理1)可见性的实现原理对于volatile关键字修饰的变量,在写入一个volatile变量时,JVM会向处理器发送一条以锁为前缀的指令,将缓存中的变量写回系统主机.在存储中。但是即使回写到内存中,如果其他处理器缓存的值还是旧的,那么在执行计算操作时就会出现问题。所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性。性协议。缓存一致性协议:每个处理器通过嗅探总线上传播的数据来检查其缓存的值是否已过期。当处理器发现自己的cacheline对应的内存地址被修改时,会更新当前处理器的cacheline设置为无效状态。当处理器要修改数据时,它会被迫再次从系统内存中读取数据到处理器缓存中。因此,如果一个变量被volatile修改,它的值将在每次数据更改后被强制刷新到主内存中。其他处理器的缓存也会将这个变量的值从主存加载到自己的缓存中,因为它们遵守缓存一致性协议。这确保了一个volatile值在并发编程中在多个缓存中是可见的。2)防止指令重排序的实现原理volatile通过内存屏障防止指令重排序。内存屏障分为以下三种:StoreBarrierStorebarrier,即x86的“sfence”指令,强制执行storebarrier指令之前的所有store指令之前的store指令。LoadBarrierLoadbarrier是x86上的“ifence”指令,在执行完loadbarrier指令后强制执行loadbarrier指令之后的所有load指令。FullBarrierFullbarrier是x86上的“mfence”指令,它结合了load和savebarrier的功能。在Java内存模型中,storebarrier是在volatile变量的写操作之后插入的,而loadbarrier是在读操作之前插入的。类的最终字段通过插入的存储屏障进行初始化,以确保在构造函数初始化并准备好使用时最终字段可见。也是JMM在读写volatile变量前后插入内存屏障指令,从而保证指令的顺序执行。