- volatile
- 1.具体含义作用
- 含义
- 线程之间共享可见的变量
- 作用
- 1.保证变量在线程间的可见性
- 2.对该变量的访问禁用指令重排
- 含义
- 2.并发编程概念
- 原子性
一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
例子
x = 10
x++
x=x+2 - 可见性
- 多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
- 有序性
- 程序执行的顺序按照代码的先后顺序执行
- 指令重排
- 单线程不影响,多线程有问题
- 原子性
- 3.物理硬件架构
- 图例
- 简单图示
- 图例2
- 图例
- 概念
- 主存 - 内存
- CPU高速缓存
- 意义
- CPU运行速度和内存读写速度差异过大,引入三级缓存防止频繁读取内存影响运行速度
- 组成
- L1
- 组成
- L1i
- 一级指令缓存
- L1d
- 一级数据缓存
- L1i
- 最近,最小,最快
- 核专属
- 一个核独享一个一级缓存
- 组成
- L2
- 二级缓存
- 核专属
- 一个核独享一个二级缓存
- L3
- 三级缓存
- 座专属
- 一个CPU座(多核)共享一个三级缓存
- L1
- 大小
- 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读取已修改的缓存数据
- Store Forwarding
- 现象
- 多个CPU之间变量的不正确同步
- 现象
- 几个变量在一个CPU里的有执行的顺序关系,但是在另一个CPU里这种关系却不成立
- 时序图
- 图例
- 原因
- 其他CPU修改某CPU的独享变量需要发送请求,但是是异步的,修改自身的独享变量是直接操作Cache
- 读取其他CPU的独享变量是通信同步的,所以是正确的
- 读取自身CPU的独享变量可以直接从Cache获取
- 读取都是同步顺序的,但如果发生变量被其他CPU修改,当前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
- 无效
- 该缓存行数据无效
- 无效
- M
- 转换
- 图例
- 状态
- MSI
- MOSI
- MESI
- 目的
- 解决多个CPU之间缓存不可见的问题
- 副作用
- CPU性能降低
- 需要与其他CPU通信,通知响应和请求数据
- 指令乱序
- 原因:为了提升CPU的运行,引入异步通信(Store Buffer&Invalid Queue),处理缓存有延迟
- 解决:提供内存屏障解决
- CPU性能降低
- 分类
- 内存屏障
- 现象
- 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()方法的开始
- 1.程序次序
- 线程工作内存与主内存的动作交互
- 图例
- 图例
- 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通过缓存一致性协议保持最新值,读取时,从主存中加载
- 总线锁
- Lock前缀指令
- 6.相关实际应用
- 1.双重校验锁定模式 - 单例模式
- 目的是避免指令重排:初始化对象与变量引用赋值可能颠倒。\n 避免其他线程拿到未初始化的对象进行操作
- 代替方案:基于内部类初始化
- 2.状态标记
- 1.双重校验锁定模式 - 单例模式
- 1.具体含义作用