参数书籍:《Java性能优化权威指南》、《Java并发编程实战》
结论:(先说结论)
- 线程安全
- 字段(field、变量、数)层次
- 语句 (语句包含 字段)层次
- volatile作用于 变量层次,无法作用 语句层次(加锁等确保)
- 所以volatile保证不了线程安全
使用情景
- volatile变量不依赖于 之前状态,不依赖于 其他变量
- 例如:volatile boolean alreadyEat
- 一个线程写变量 我吃了 alreadyEat = true。 3、4个线程读 if(alreadyEat)playGame ,
- volatile的局限性就是 playGame这一串逻辑,不可以再写变量 alreadyEat (否则就是依赖了 alreadyEat == true这个状态)
- 不适用情景就是 if (isDoorOpen) isDorOpen = false, 这种你要用原子变量 AtomicBoolean isDoorOpen, if(isDoorOpen.getAndSet(flase))
- AtomicBoolean这种是CAS compare and swap,是可以硬件级别保证 原子性,而且还不 阻塞线程。 very good
大白话(2020/01/30补充):
- CPU 存储(寄存器、register、高速缓存),RAM(内存、内存条)
- CPU核心有 运算、存储 两个功能
- RAM也有存储,两个存储,就导致了数据不一致
- CPU多核心,核心之间、内存之间 也会数据不一致
- 简单的举例下,正确的以后补充
- CPU从RAM读取变量 varable1、varable2 保存在 register1、register2
- 相加 保存在register3
- register3写回到RAM
volatile作用:
- volatile字段值 在内存、CPU寄存器中一致
- 通过 内存屏障 实现
- 无论那个CPU寄存器 更改,数据都一致
内存屏的内核实现
#include <linux/kernel.h>
// 插入内存屏障,对硬件无影响
void barrier(void)
// 把 CPU寄存器中的所有修改的数值 写入内存。
// 可屏蔽编译器优化
// 但对 硬件的指令重排 无效
#include <asm/system.h>
//出入硬件内存屏障,实现平台相关
//保屏障前后 读操作的不乱序
void rmb(void); //(读内存屏障)
//不推荐,推荐用rmb
void read_barrier_depends(void);
//写操作不 乱序
void wmb(void);
//读写操作不乱序
void mb(void);
线程安全 条件:
- 可见性
- 变量层次:数据的更改,对所有的CPU的register都一致(你更改,我能看见)
- 原子性
- 变量层次:比如 double 变量运算
- 代码层次:生产者、消费者问题,if***then***。(volatile保证不了)
- 指令重排()
- volatie可以阻止,但实际是
- 代码变成机器指令,在硬件层次、编译层次 会重新排序 优化。
为什么volatile保证不了线程安全
volatile只能作用于 变量层次、无法作用于 语句层次(加锁等确保)
举例:
if(volatile no exist)
then new volatile.
- if的判断volatile保证当时确实正确,然后线程a可能睡着了,
- 线程b也判断不存在,b线程就new了一个。
- 然后a线程wake up,据需执行new volatile。
就有了两个volatile的对象,
怎样变成线程安全
- 保证代码层次的 原子性
- 本例:对 if--》create这个过程加锁
- 后者 代码层次 本身就原子性
实现原理:
CPU缓存中的volatile字段被一个线程修改后,其他CPU缓存中的线程在读 本地CPU缓存的 volatile字段时,就必须读取更新过的字段。
具体是 在出现volatile字段的地方加入一条CPU指令:内存屏障(通常称为membar后fence),一旦volatile字段变化,就会触发CPU缓存更新
缺点:
- 频繁更改、改变或写入volatile字段 有可能导致性能为题。(volatile字段未修改时,读取没有性能问题的)
- 限制现代JVM的JIT编译器对这个字段优化(volatile字段必须遵守一定顺序,但这也是优点,或者说是特点吧!本来就是要保证happen-before,放置顺序指令重拍导致bug 例如单例双检测bug)
性能优化:
- 减少对volatile的写操作
- 重构避免使用volatile