深入java内存模型(二) volatile详解

  对于volatile修饰符,我们应该不会陌生,在多线程中使用常见,比如多个线程想用一个全局变量作为标识符,或者一个共享变量,我们都会给该变量加上一个volatile的修饰符。volatile用中文解释是易变的,不稳定的。说明该变量会被多个线程访问并可能修改。那么jvm是怎样发挥volatile关键字的作用,如何实现的呢?

  上一篇深入java内存模型中解释了jvm中的重排序以及四种内存屏障等。jvm总是会以一些易懂,使用方便的方式来实现相关功能。比如垃圾回收器,对于内存的申请与释放时一个令人头疼的问题。jvm通过垃圾回收器实现了自动管理,安全又方便。对于volatile这个也一样。jvm对于volatile的功能定义为,对于该变量的读和写具有原子性,对于该变量的修改,立刻对其他线程可见。我们知道,对一个变量的写入,其实至少是两步,第一步,改变本地变量的值,该变量则位于写缓冲中。第二步则是把写缓冲更新到共享内存中,这样其他线程就能看到改变。要保证该操作的原子性,那么这两步必须一次执行。对于这个,jvm实现如下:

比如定义一个 volatile boolean变量 flag

  线程A写入本地内存后,会直接将缓存刷新到主内存中,如此该变量对其他线程可见。

  线程B读取flag时,会将本地内存中的flag设置失效,直接从主内存中读取flag,这样读到的flag则是最新值。

上面是jvm对volatile表现出来的一个执行方式。但是指令是一条一条执行的。从指令控制上来讲,jvm又是怎样实现的呢。

下图是jvm对volatile读写与普通读写的指令是否允许冲排序的一个表。

从表中我们可以看出:

  1,如果第二个操作是votatile写,那么前面的任何指令都不能与它重排序,即前面的不会排到volatile写之后。

  2,如果第一个操作是volatile读,那么后面的任何指令都不能与它重排序,即后面的不会排到volatile读之前。

通常,jvm对于指令的重排控制是在中间插入内存屏障。

JMM中的定义如下:

  在volatile写之前插入一个StoreStore屏障。

  在volatile写之后插入一个StoreLoad屏障。

      在volatile读之后插入一个LoadLoad屏障。

  在volatile读之后插入一个LoadStore屏障。

如此则达到了volatile的语义效果。我们可以用两个图来形象表示该过程:

通过了解volatile的具体功能与底层实现,对于它的使用就能运用的更得心应手,它体现了java内存模型与java线程的关系。对我们有效与正确的开发有很大的作用。如果上面内容理解起来不是很容易,用一个经常使用的锁可以形象描述他的功能,即:每次对volatile变量的写与读,得获取该变量的锁,才能执行。写入时,对该变量加锁,当把写入值刷新到内存后,释放该锁。读取时,对该变量加锁,当从共享内存中把值读取后,释放锁。希望能给大家一点帮助。

  

原文地址:https://www.cnblogs.com/2015zzh/p/4954145.html