Java volatile 理解

1. 现代CPU Cache结构

多核 CPU Cache 结构
1.1 缓存的主要作用

现代多核CPU为了提升处理速度,都会将需要的数据从内存拷贝到各自的缓存中(L1,L2),然后在各自的缓存中对数据进行操作。

1.2 缓存的作用范围

L1,L2 是CPU中每个核私有的,用于备份各自所需要的数据。L3 Cache是CPU每个核共享的。

1.3 缓存的写入模式

写回(write-back)模式:各个核每次修改自己缓存中的数据后不会立即写回到内存,而是等到一定合适的时间才写回到内存。
直写(write-through)模式:各个核每次修改自己缓存中的数据后会立即写回到内存。

1.4 缓存的一致性

无论哪种写入模式,试想,如果多个核都从内存缓存了一份相同的数据,而此时有一个核将自己缓存中的数据进行了修改,其他核缓存中的数据却依然是修改之前的数据,这就造成了各个核之间缓存数据的不一致,而我们所期望的是各个核缓存之间的数据可以同步,为了达到同步的目的,各个核的缓存需要共同遵守一份协议,保证修改共享数据的时候可以通知其他缓存,这就是CPU的缓存一致性协议。

缓存一致性协议有多种,但是基本上都是基于MESI协议进行扩展的,详细的内容可以查阅相关文档了解。

2. volatile的实现原理

2.1 可见性

可见性,即一个线程修改一个共享变量时,其他线程可以获取到修改后的值。JVM的实现中,volatile共享变量的写操作会向处理器发送一条Lock指令,声言使用直写模式,在CPU缓存一致性协议的保证下实现volitile共享变量的可见性。

简单来讲,多核环境下,对volatile共享变量进行写操作会将缓存的结果直接写回到内存中,在缓存一致性协议下,其他核会对总线上传输的数据一直进行窥探,即监测其他核对缓存数据的操作。因此,volatile共享变量写回内存的操作会被其他核监测到,此时这些核会将自己缓存中的数据设为无效状态,当需要对这个数据进行修改操作的时候会重新从内存中读取,这样就达到了volatile修饰变量的可见性。

2.1 原子性

volatile修饰的变量无论是读还是写操作,都是具备原子性的,可以理解为使用了锁:

volatile int value = 1;
public void set(int value) {
   this.value = value;
}
public void get() {
   return this.value;
}
int value = 1;
public synchronized void set(int value) {
   this.value = value;
}
public synchronized int get() {
   return this.value;
}

这意味着多线程环境下任何一个线程读取到的volatile修饰的共享变量都是当前的最新值,不会存在差异性。
但是有一点需要注意,volatile只是保证了读/写的原子性,复合的volatile操作并不保证原子性,例如:

volatile int value = 1
public void set(int value) {
   this.value = value++;
}

因为value++这个操作可以分为三个步骤,首先,读取value的值,其次,
进行自增操作,最后,将结果写入内存。需要知道这三个操作volatile并不能保证原子性,即只能保证读取到的value是最新值,如果此时该处理器读取value之后由于某些原因阻塞,而此时其他处理器刚好对value进行了修改,这个时候之前的处理器进行计算时还是使用之前读取到的value,这样就造成了错误的处理结果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,394评论 11 349
  • 擦肩而过,没有遇上博子的藤井,回家看到信,匆匆写了一封回信,讲述了自己曾有一个高中时代的同班男生,和她同名同姓。博...
    鱼耗子阅读 195评论 0 0
  • 前言 好了,一个大龄老男人的悲情回忆环节结束,下面开始正题。 个人认为想要在安卓开发的道路上走的很长远,J2SE的...
    尼古拉斯_富貴阅读 137评论 0 0