Java核心复习——volatile
一、介绍
volatile保证共享变量的“可见性”。可见性指的是当一个线程修改变量时,另一个线程能读到这个修改的值。
这里就要提出几个问题。
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。- 问题1:为什么一个线程修改时,另一个线程可能会“看不见”?
- 问题2:这种可见性是如何实现的?
二、问题1 变量为何“不可见”
CPU的处理速度远远快于内存的读取速度,所以处理器不直接和内存通信。CPU与内存之间的瓶颈也叫“冯诺依曼瓶颈”。
为了解决这个问题,引入内部缓存(L1、L2...),内存的数据会读取到L1、L2中,CPU直接从L1、L2中读取数据处理。
但如果CPU操作了数据,并写回到L1、L2中,L1、L2不知何时会写回到内存。
这里插入一个问题?为什么要分L1、L2,它们有何不同。
2.1 L1、L2、L3
L1、L2、L3是CPU三级缓存,CPU缓存的定义为CPU与内存之间的临时数据交换器,它的出现是为了解决CPU运行处理速度与内存读写速度不匹配的矛盾。
读取速度 L1 > L2 > L3
容量大小 L1 < L2 < L3
L1又分为一级数据缓存、一级指令缓存,分别用于存放数据、执行数据的指令解码。
2.2 只管高速缓存中的变量,不管内存中的变量行不行?
这里又有一个问题,多个处理器修改变量写到高速缓存,高速缓存中存储的变量值是最新,下次不管内存中的变量值,直接从高速缓存中获取不就行了?
回答:当然不行。
为什么?因为每个处理器都有自己的高速缓存,一个变量在A处理器的高速缓存中修改,B处理器可不能感知。
2.3 产生并发Bug的源头
- 可见性
- 缓存导致的可见性问题
- 原子性
- 线程切换带来的原子性问题
- 有序性
- 编译优化带来的有序性问题
三、问题2 变量如何变为“可见”
为了保证在多处理器下,缓存一致,就设置一个缓存一致的协议。每个处理器通过嗅探在总线传播的数据来检查自己缓存的值是否是过期的。
当处理器发现自己的缓存行对内的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当需要对数据进行修改时,重新从内存中读到高速缓存中。
3.1 具体是如何实现?
当变量被volatile修饰,编译成汇编时,会增加一个Lock前缀指令。
Lock前缀指令的作用:
- Lock前缀指令,会让处理器缓存回写到内存
- 一个处理器缓存回写到内存,导致其他处理器的缓存无效
四、并发编程的学习路线图
参考文档
《Java并发编程的艺术》
Java并发编程-volatile
CPU与内存的那些事
超能课堂(133):为什么CPU缓存会分为L1、L2、L3?
《Java并发编程实战——王宝令》
