总线嗅探是通过CPU侦听总线上发生的数据交换操作,当总线上发生了数据操作,那么总线就会广播对应的通知,CPU收到通知后,再根据本地的情况进行响应 。
5. 有序性问题虚拟机在进行代码编译时 , 对改变顺序后不会对最终结果造成影响的代码,虚拟机不一定会按我们写的代码顺序运行,有可能进行重排序 。实际上虽然重排后不会对变量值有影响 , 但会造成线程安全问题 。
重排序又可以分为三种:
- 编译器优化的重排序 。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
- 指令级并行的重排序 。现代CPU采用了指令级并行技术来将多条指令重叠执行 。对于不存在数据依赖的指令,CPU可以改变语句对应机器指令的执行顺序
- 内存系统的重排序 。由于CPU使用三级缓存结构,这使得数据加载和存储操作看上去可能是在乱序执行的
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()方法的开始
而volatile是通过插入内存屏障(Memory Barrier),在内存屏障前后禁止重排序优化,以此实现有序性 。
内存屏障有两个作用:一是保证特定操作的执行顺序,二是保证某些变量的内存可见性 。
volatile内存语义的实现: JMM 针对编译器制定的 volatile 重排序规则表
操作普通读写volatile读volatile写普通读写可以重排可以重排不可以重排volatile读不可以重排不可以重排不可以重排volatile写可以重排不可以重排不可以重排编译器在生成字节码时 , 会在指令序列中插入内存屏障来禁止特定类型的处理器重排序:
- 在每个volatile写操作的前面插入一个StoreStore屏障
- 在每个volatile写操作的后面插入一个StoreLoad屏障
- 在每个volatile读操作的后面插入一个LoadLoad屏障
- 在每个volatile读操作的后面插入一个LoadStore屏障
比如:第二种场景常见的就有修饰单例模式的对象 。
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的数据安全性 。文章插图
我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见
推荐阅读
- pytorch、paddlepaddle等环境搭建 深度学习环境搭建常用网址、conda/pip命令行整理
- 6 Java多线程:锁与AQS(下)
- 三十九 Java开发学习----SpringBoot整合mybatis
- JavaSPI详解
- day04-JavaScript01
- Java安全之Tomcat6 Filter内存马
- JavaScript的异步编程之Promise
- 5 Java多线程:CAS
- java中HashMap的设计精妙在哪?
- JAVA系列之JVM内存调优