谈谈你对volatile的理解
1 什么是volatile
首先volatile是Java虚拟机提供的轻量级的同步机制,相当于于一个轻量级的锁,其有如下的特点:
- 保证可见性
- 不保证原子性
- 保证有序性–禁止指令重排
2 volatile特点的解析
2.1 可见性
说起可见性,我们就需要了解到一个概念:JMM模型
1 为什么存在JMM?
在早期,计算机的cpu的计算速度和内存的存取速度是差不多,但是到了现在cpu的运算速度高于内存的存取速度好几个数量级,因此为了整体的运行速率,我们不得不加上一层读写速度尽可能接近处理器的运算速度的高速缓存,作为内存和cpu之间的缓冲。
- 将需要运算的数据复制到高速缓存中,让cpu运算快速进行,无序等待内存的读写。
这种方式缓和了处理器和内存的速率的差异,但是带来了新的问题:缓存一致性。在实际过程中,每个处理器都有自己的高速缓存,他们又共享同一个主内存,模型图如下所示。
问题1:多个处理器的运算任务都涉及到同一块主内存区域的时候,将可能导致各自的缓存不一致,如何解决?
答案:MESI(Inter缓存一致性协议)
当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从主内存重新读取。
问题2:cpu是如何发现数据失效?
答案:总线嗅探
每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址的值被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
缺点:可能会出现总线风暴
由于Volatile的MESI缓存一致性协议,需要不断的进行总线嗅探和cas不断循环,无效交互会导致总线带宽达到峰值。所以不要大量使用Volatile,至于什么时候去使用Volatile什么时候使用锁,根据场景区分,接下来需要说到JMM模型。
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范。通过规范定制了程序中各个变量(线程共享变量)的访问方式,以及如何将变量存储到内存中的细节问题。
首先是变量拷贝和存储的过程:
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称之为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域(方法区和堆),所有线程都可访问。
但线程对变量的操作(读取,修改等)必须在工作内存中进行,因此首先要将变量从主内存拷贝到自己的工作内存,然后对变量进行操作,操作完成再将变量写回主内存,而不是直接操作主内存中的变量。
因为不同的线程无法访问对方的工作内存,多线程间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:
这样的机制也导致了可见性问题的出现,因此我们需要解决可见性的问题。
2 可见性
通过前面对JMM的介绍,我们知道各个线程对主内存中共享变量的操作都是各个线程各自拷贝变量到自己的工作内存操作后,再写回主内存中的。这就可能存在一个线程AAA修改了共享变量X的值还未写回主内存中时,另外一个线程BBB又对内存中的一个共享变量X进行操作,但此时A线程工作内存中的共享变量的值,对线程B来说是不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题。
既然出现了可见性的问题那么如何解决?
1)加锁