Volatile

Volatile

特性:保证可见性,防止指令重排,不保证原子性

JMM

三大特性:可见性、原子性、有序性

八大原子操作

read读取:将主内存中的变量加载到工作内存

load加载:将工作内存中的数据赋给工作内存中的变量

use操作:将赋值后的变量进行计算操作

assign赋值:将计算后的数据赋给工作内存中的变量

store存储:将赋值后的数据写回主内存

write写入:将上一步主内存中的数据赋值给对应的变量

lock加锁:将变量标记为线程独占状态

unlock解锁:将加锁的标记解除

lock和unlock介入时机

被volatile修饰的对象在store之前会对主内存中变量加锁lock,加锁后其他线程将无法对主内存对应的变量进行操作,当持有锁执行完store和write后,会释放锁unlock。

因为lock--unlock之间就是单纯的内存操作,所以速度非常快,几乎不影响性能。

为什么可以保证可见性( MESI缓存一致性协议)

例如:

​ 1. 定义一个变量 public volatile int i=0;

​ 2. A、B、C线程同时将i加载到工作内存中

​ 3. A线程完成了load操作;B线程完成了assign操作(i+1);C线程完成了store操作

​ 4. 当C线程完成store操作后,因为变量i是被volatile修饰的,所以会通过MESI协议分别通知 A线程和B线程,将A\B线程中工作内存中的变量i进行失效(我理解为将i=null)

​ 5. 这时A\B线程再继续操作的时候发现i=null了,就要去主内存中获取(read)新的值,这样 就保证了可见性

其他相关信息:

  1. 在第五步,read时线程C还没完成unlock操作,那么线程会阻塞等待;

  2. 如果没有被volatile修饰,在执行完assign操作后,线程会继续执行后面业务的操 作,不确定什么时候才会将i变量调用store\write写回主内存;如果被volatile修饰 了,即便后面还有很多业务流程,线程也不会去执行,线程会立刻执行store\write 操作

为什么不保证原子性

在上面的例子中

  1. 初始状态下i=0
  2. A\B\C线程同时将i=0加载到工作内存中,而B线程完成了use操作(i+1),并且将1写回了工作内存中,这时B线程工作内存变量i值为1;
  3. C线程完成了store操作,并且完成write操作,此时主内存中变量i值由0变为1;

并且通过MESI协议使A\B线程工作内存中的变量i失效;

  1. 这时,线程B要执行store操作了,发现工作内存中的i失效了,便重新从主内存中读取一份,然后再执行store操作,但这时读取i的值为1,这个1是线程C计算得出的,而不是线程B计算得出的,丢失了B线程操作的记录

以上,就是为什么volatile不保证原子性原因

如何避免不保证原子性

  1. synchronized
  2. lock
  3. Atomic原子类

防止指令重排

例如:new 一个User对象 User user=new User();

通过javap命令得到字节码文件可得知,new操作分为三步

  1. 在栈上创建内存
  2. 在堆中初始化对象
  3. 将栈和堆进行引用

如果user没有volatile修饰,那么执行流程可能是123,132,312。。。。

加上了修饰,只会是123.

这个在单例模式中体现最为明显,如果不加修饰,可能会导致有引用,但为初始化,导致报错

八个原子操作流程图

read->load->use->assign->lock->store->write->unlock

虚线嗅探机制为实时监听中。

红色线表示不保证volatile原子性的阶段

img

posted @ 2020-07-29 11:50  RollBack2010  阅读(152)  评论(0编辑  收藏  举报