深度剖析Java的volatile实现原理,再也不怕面试官问了( 二 )


总线嗅探是通过CPU侦听总线上发生的数据交换操作,当总线上发生了数据操作,那么总线就会广播对应的通知,CPU收到通知后,再根据本地的情况进行响应 。
5. 有序性问题虚拟机在进行代码编译时 , 对改变顺序后不会对最终结果造成影响的代码,虚拟机不一定会按我们写的代码顺序运行,有可能进行重排序 。实际上虽然重排后不会对变量值有影响 , 但会造成线程安全问题 。
重排序又可以分为三种:

  • 编译器优化的重排序 。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
  • 指令级并行的重排序 。现代CPU采用了指令级并行技术来将多条指令重叠执行 。对于不存在数据依赖的指令,CPU可以改变语句对应机器指令的执行顺序
  • 内存系统的重排序 。由于CPU使用三级缓存结构,这使得数据加载和存储操作看上去可能是在乱序执行的
不过重排序也不是随便重排的 , 发生指令重排序的前提是:在单线程下不影响执行结果、对没有数值依赖的代码进行重排序 。这就是as-if-serial语义 。在多线程情况下有一套更具体的规则,那就是happens-before原则 。
happens-before由以下八大原则组成:
  • 程序次序规则:一个线程内 , 按照代码顺序,书写在前面的操作先行发生于书写在后面的操作(线程的执行结果有序)
  • 锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
  • 传递规则:如果操作A先行发生于操作B,操作B先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的start()方法先行发生于该线程的其他任何操作
  • 线程中断规则:对线程中断方法interrupt()的调用先行发生于被中断线程检测到中断事件的发生
  • 线程终结规则:线程中所有操作先行发生于线程的终止检测 。通过Thread.join()方法结束、Thread.isAlive()方法的返回值等手段检测到线程已经终止执行 。比如在A线程中调用B.join()方法 , B线程执行完成后,B对共享变量的修改,对A来说是可见的
  • 对象终结规则:一个对象的初始化方法完成先行发生于该对象的finalize()方法的开始
如果两个操作不满足上述八大原则中的任意一个,那么这两个操作就没有顺序保证,虚拟机可以对这两个操作进行重排序 。如果操作A happens-before 操作B , 那么A在内存所做的修改对B都是可见的 。
而volatile是通过插入内存屏障(Memory Barrier),在内存屏障前后禁止重排序优化,以此实现有序性 。
内存屏障有两个作用:一是保证特定操作的执行顺序,二是保证某些变量的内存可见性 。
volatile内存语义的实现: JMM 针对编译器制定的 volatile 重排序规则表
操作普通读写volatile读volatile写普通读写可以重排可以重排不可以重排volatile读不可以重排不可以重排不可以重排volatile写可以重排不可以重排不可以重排编译器在生成字节码时 , 会在指令序列中插入内存屏障来禁止特定类型的处理器重排序:
  • 在每个volatile写操作的前面插入一个StoreStore屏障
  • 在每个volatile写操作的后面插入一个StoreLoad屏障
  • 在每个volatile读操作的后面插入一个LoadLoad屏障
  • 在每个volatile读操作的后面插入一个LoadStore屏障
6. volatile应用场景volatile可以保证可见性和有序性,但无法保证原子性 。所以它的应用场景就不如synchronized广泛,主要有两个场景:一是做状态变量 , 二是做需要重新赋值的共享对象 。
比如:第二种场景常见的就有修饰单例模式的对象 。
public class Singleton {// 使用volatile修饰,赋值后,其他线程能立即感知到private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}}还有就是CopyOnWriteArrayList的底层实现就是用volatile修饰的数组,因为CopyOnWriteArrayList每次修改数据后都会数组重新赋值,而不是只修改数据中的一个值,这样才能保证了CopyOnWriteArrayList的数据安全性 。
深度剖析Java的volatile实现原理,再也不怕面试官问了

文章插图
我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见

推荐阅读