• volatile
    • 1.具体含义作用
      • 含义
        • 线程之间共享可见的变量
      • 作用
        • 1.保证变量在线程间的可见性
        • 2.对该变量的访问禁用指令重排
    • 2.并发编程概念
      • 原子性
        一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
        例子
        x = 10
        x++
        x=x+2
      • 可见性
        • 多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
      • 有序性
        • 程序执行的顺序按照代码的先后顺序执行
        • 指令重排
          • 单线程不影响,多线程有问题
    • 3.物理硬件架构
      • 图例
        • 简单图示
        • 图例2
        • 图例
      • 概念
        • 主存 - 内存
        • CPU高速缓存
          • 意义
            • CPU运行速度和内存读写速度差异过大,引入三级缓存防止频繁读取内存影响运行速度
          • 组成
            • L1
              • 组成
                • L1i
                  • 一级指令缓存
                • L1d
                  • 一级数据缓存
              • 最近,最小,最快
              • 核专属
                • 一个核独享一个一级缓存
            • L2
              • 二级缓存
              • 核专属
                • 一个核独享一个二级缓存
            • L3
              • 三级缓存
              • 座专属
                • 一个CPU座(多核)共享一个三级缓存
          • 大小
            • lscpu查看
        • CPU缓存行
          • 定义
            • CPU缓存的基本单位
          • 大小
            • 2的整数幂个连续字节,一般为32-256个字节
            • 常见为64字节
          • 作用
            • 缓存内存空间中连续的一段数据
            • 数组将缓存其后相邻的元素
              • 加快顺序访问
            • 字段将缓存相邻的几个字段
              • 伪共享
        • Store Buffer
          • CPU核心与CPU缓存之间的一个存储缓冲
          • 作用
            • 目的是为了提升本CPU效率,将同步变异步
            • 将写非本CPU的独享变量与同步通知其他CPU把缓存置为无效的过程通过异步处理
              • 1.当前CPU修改不是自己的独享变量
              • 2.1写入本CPU的Store Buffer
              • 2.2通知其他CPU设置无效缓存行
              • 1.获得响应ACK
              • 2.将Store Buffer中的对应数据写入Cache
          • 副作用
            • 单个CPU内的指令乱序
              • 现象
                • 本CPU修改其他CPU的独享变量后,马上读取还是原来的值(有延迟,好比和后续代码顺序调换)
                • 图例
              • 原因
                • 最开始的设计:(对非本CPU的独享变量)写操作先缓存至Store Buffer,读从直接从相应CPU那获取;写入Store Buffer与写入Cache是有延迟的(通知与等待ACK)
              • 解决
                • Store Forwarding
                  • 直接从Store Buffer读取已修改的缓存数据
            • 多个CPU之间变量的不正确同步
              • 现象
                • 几个变量在一个CPU里的有执行的顺序关系,但是在另一个CPU里这种关系却不成立
                • 时序图
                  • 图例
              • 原因
                • 其他CPU修改某CPU的独享变量需要发送请求,但是是异步的,修改自身的独享变量是直接操作Cache
                • 读取其他CPU的独享变量是通信同步的,所以是正确的
                • 读取自身CPU的独享变量可以直接从Cache获取
                  • 读取都是同步顺序的,但如果发生变量被其他CPU修改,当前CPU的缓存行状态变化可能就会不及时
              • 解决
                • 内存屏障
        • Invalid Queue
          • 一个队列,用于存储其他CPU向当前CPU发出的请求,置某些缓存行为无效的消息
          • 作用
            • 目的是为了提升其他CPU效率
            • 加快响应其他CPU发出的置缓存行为无效的请求
              • 直接返回ACK
              • 将消息放入队列中,后续处理
                • 处理时机:处理任何缓存行的MSEI状态前
                • 遗漏:本CPU的独享变量读取
                  • 使用读屏障解决
                    • 遇到读屏障会把队列的消息处理了,然后处理屏障后的操作
      • 影响
        • 缓存一致性
          • CPU缓存加快数据的访问数据,但是多线程下对共享变量的读写将会存在延时,影响可见性
        • 指令重排
          • 为了提升CPU的运行速度,但破坏了指令的有序性
          • 分类
            • 主动的
              • 编译器优化,使得CPU更快执行指令
            • 被动的
              • CPU流水线操作延迟。为了异步化指令的执行,引入Store Buffer和Invalidate Queue,却导致了「指令顺序改变」的副作用
      • 处理
        • 总线锁
          • 阻塞其他CPU访问内存,效率低下,早期采用的方案
        • 缓存一致性协议
          • 分类
            • MESI
              • 状态
                • M
                  • 修改
                    • 该缓存行有效,数据被修改了,和内存中的数据不一致,数据只存在于本缓存行中
                • E
                  • 独享
                    • 该缓存行有效,数据和内存中的数据一致,数据只存在于本缓存行中
                • S
                  • 共享
                    • 该缓存行有效,数据和内存中的数据一致,数据同时存在于其他缓存中
                • I
                  • 无效
                    • 该缓存行数据无效
              • 转换
                • 图例
            • MSI
            • MOSI
          • 目的
            • 解决多个CPU之间缓存不可见的问题
          • 副作用
            • CPU性能降低
              • 需要与其他CPU通信,通知响应和请求数据
            • 指令乱序
              • 原因:为了提升CPU的运行,引入异步通信(Store Buffer&Invalid Queue),处理缓存有延迟
              • 解决:提供内存屏障解决
        • 内存屏障
          • 现象
            • Load-Store
            • Store-Load
            • Load-Load
            • Store-Store
          • 分类
            • 写屏障
              • 分类
                • 简单刷入,等待刷入完再继续执行,即变为同步
                  • 一步一步完成,完成一个指令再执行下一行指令
                • 还是异步,把屏障后的操作也暂时缓存,一起刷入
                  • 写屏障后的写操作也跟同(写屏障标记的操作)写入Store Buffer中先缓冲,\n使得其他CPU想获取当前CPU的独享变量只能获取Cache中之前的值, \n直到当前CPU在适当时机(不太清楚,应该是获得ACK),才会把写屏障标记及之后的数据一同写入Cache
                  • 实现写的可见性,写数据顺序的可见性
                    • 1.写远程缓存数据
                    • 2.写本地缓存数据
                      • 写屏障后的本地写操作
                    • 3.获取到远程响应
                  • 解决的问题
                    • 远程写是异步的,本地写是同步的 \n远程写是往Store Buffer,本地写是往Cache \n实现原理:遇到写屏障把写屏障标记及之后的缓存都写入Store Buffer(即便是本地写),解除标记时全部写入Cache
                  • 把写屏障与获得ACK之间的写操作视为一个整体缓存和再刷入
              • 区别
                • 同步的对其他CPU而言可以更快拿到缓存更新结果,\n 异步的对当前CPU而言效率更高,但是对其他CPU而言需要晚一步
            • 读屏障
              • 遇到读屏障,则将Invalid Queue中的数据全部处理完
              • 实现读的可见性,读(远程写)的可见性
                • 1.响应远程写
                • 2.远程写消息放入 Invalid Queue,待处理
                • 3.已经可以读到远程CPU(它自己本地的)缓存数据的修改
                • 4.想要当前CPU之前独享的缓存数据
                  • 按道理,已经可以读到远程CPU修改过(它本地)的变量,\n那么远程CPU修改过(当前CPU)的变量应该同样可见才是,\n 因为远程CPU写屏障已经解除了(已经获取到了ACK),\n但实际当前CPU还没有处理,消息依旧在Invalid Queue中,
                  • 遇到读屏障,将已无效的缓存处理掉,之后不再从Cache中读,以防脏读
                • 5.远程读(之前远程写)相应的缓存行
                  • 获取正确的缓存
              • 解决的问题
                • 远程读是同步的,本地读是同步的,处理远程写是异步的\n 本地读直接从Cache中获取,远程写的通知消息可能还在在Invalid Queue中
            • 全屏障
    • 4.JVM内存模型
      • 图例
        • JVM内存模型
      • 意义
        • 屏蔽硬件和操作系统访问内存的差异,保证在各个平台和系统对内存的访问效果一致
        • 对CPU物理硬件层的抽象
      • 抽象结构
        • 主内存
          • 共享变量的存储位置
        • 工作内存
          • CPU寄存器和高速缓存
          • 线程对变量的操作发生的位置
          • 线程不能直接读写主内存
      • happen-before规则
        • 1.程序次序
          • 一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
        • 2.锁定规则
          • 一个unLock操作先行发生于后面对同一个锁额lock操作
        • 3.共享变量规则
          • 对一个变量的写操作先行发生于后面对这个变量的读操作
        • 4.传递规则
          • 如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
        • 5.线程启动规则
          • Thread对象的start()方法先行发生于此线程的每个一个动作
        • 6.线程中断规则
          • 对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
        • 7.线程终结规则
          • 线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
        • 8.对象终结规则
          • 一个对象的初始化完成先行发生于他的finalize()方法的开始
      • 线程工作内存与主内存的动作交互
        • 图例
    • 5.JVM实现原理
      • Lock前缀指令
        • 总线锁
          • 总线只有一条,且为独占的,同一时间,只有一个CPU能占用总线
          • 当通过LOCK#信号锁定总线后,其他CPU访问内存的请求将被阻塞,直到总线被释放,保证了读写的原子性
          • 主要使用场景
            • 1.处理器不支持缓存锁定,比如 Intel486、Pentlum
            • 2.处理的数据不能被缓存在CPU时
            • 3.操作的数据跨多个缓存行时
        • 缓存锁
          • 当前CPU修改变量,会直接写入内存中
          • 缓存一致性协议,阻止同时修改
          • 大致过程
            • 1.线程A执行,写volatile变量,执行到Lock前缀指令
            • 2.Lock前缀指令,锁定缓存行
            • 3.其他线程通过总线嗅探获知消息,然后将各自工作内存中的相应的缓存置为无效
            • 4.线程A将最新数据写入主存
            • 5.释放缓存行的锁
            • 6.其他CPU通过缓存一致性协议保持最新值,读取时,从主存中加载
    • 6.相关实际应用
      • 1.双重校验锁定模式 - 单例模式
        • 目的是避免指令重排:初始化对象与变量引用赋值可能颠倒。\n 避免其他线程拿到未初始化的对象进行操作
        • 代替方案:基于内部类初始化
      • 2.状态标记

延伸