volatile
jmm8个原子性操作
- lock :将一个变量标记为线程独占;
- read :从主内存中读取数据;
- load :将读取的数据加载到线程工作内存中;
- use :将工作内存的数据给执行引擎执行;
- assign :将执行结果赋值给工作内存;
- store :将工作内存的数据存回主内存;
- write :将线程存回主内存的数据写入原始变量;
- unlock :释放锁;
流程图
总线:就是主内存和cpu数据传输的通道
问题
并发下,数据一致性没法保证。
解决方式一(加锁)
加锁 – 我们看到还有lock和unlock两个操作在流程图上没有体现。
这是在数据操作的整个流程加锁;
缺点也很明显:数据操作串行化了、效率很低。
解决方式二(MESI缓存一致性协议)
1、cpu有个总线嗅探机制 – 监听;
2、数据store的时候经过总线;
3、cpu会感知到变量已经改变了;
4、cpu将自己工作内存中的该副本变量置为失效 – 失效了再操作的时候就会从新从主内存read。
这样还是会存在问题:多个线程都在进行store怎么办?
例如:两个线程都在store,相继执行了write,而有其他线程在两个线程write中间read;这样读取到的不是最新的数据。
解决:加锁 – 将store和write加锁。保证这两步的原子性
ok…volatile就是帮我们做了这些事
1、开启缓存一致性协议和cup嗅探机制;
2、加锁,保证store和write的原子性。
volatile的加锁属于局部加锁,这样既保证了一致性,又保证了并发性。因为cpu操作很快,两个原子操作同步基本上不会影响性能。
如果volatile修饰后,将我们的代码反汇编后是可以看到assign操作有个lock前缀指令
cup看到lock指令:
1、会将缓存数据立刻写会主内存;
2、volatile是在store前lock;
3、触发MESI协议 – 其他cpu失效自己的数据。
可见性:一个线程修改了数据,另外一个线程可见/感知到。
附带一个volatile经典例子:
// volatile boolean flag = false;
boolean flag = false;
public void volatileTest(){
new Thread(() -> {
while (!flag){}
MyLogger.logger.info("我跳出来了");
}, "线程一").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {}
MyLogger.logger.info("我修改状态");
flag = true;
}, "线程二").start();
}