我们知道动荡的关键字的作用是保证变量在多线程之间的可见性,它是java . util . concurrent包的核心,没有挥发性就没有这么多的并发类给我们使用。
本文详细解读一下波动的关键字如何保证变量在多线程之间的可见性,在此之前,有必要讲解一下CPU缓存的相关知识,掌握这部分知识一定会让我们更好地理解不稳定的原理,从而更好,更正确地地使用不稳定的关键字。
,因为CPU运算速度要比内存读写速度快得多,举个例子:
-
<李>一次主内存的访问通常在几十到几百个时钟周期李>
<李>一次L1高速缓存的读写只需要1 ~ 2个时钟周期李>
<李>一次L2高速缓存的读写也只需要数十个时钟周期李>
这种访问速度的显著差异,导致CPU可能会花费很长时间等待数据到来或把数据写入内存。
基于此,现在CPU大多数情况下读写都不会直接访问内存(CPU都没有连接到内存的管脚),取而代之的是CPU缓存,CPU缓存是位于CPU与内存之间的临时存储器,它的容量比内存小得多但是交换速度却比内存快得多。而缓存中的数据是内存中的一小部分数据,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可先从缓存中读取,从而加快读取速度。
按照读取顺序与CPU结合的紧密程度,CPU缓存可分为:
-
<李>一级缓存:简称L1缓存,位于CPU内核的旁边,是与CPU结合最为紧密的CPU缓存李>
<李>二级缓存:简称L2缓存,分内部和外部两种芯片,内部芯片二级缓存运行速度与主频相同,外部芯片二级缓存运行速度则只有主频的一半李>
<李>三级缓存:简称L3缓存,部分高端CPU才有李>
每一级缓存中所存储的数据全部都是下一级缓存中的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也相对递增。
当CPU要读取一个数据时,首先从一级缓存中查找,如果没有再从二级缓存中查找,如果还是没有再从三级缓存中或内存中查找。一般来说每级缓存的命中率大概都有80%左右,也就是说全部数据量的80%都可以在一级缓存中找的到,只剩下20%的总数据量才需要从二级缓存,三级缓存或内存中读取。
用一张图表示一下CPU,在CPU缓存,在主内存数据读取之间的关系:
当系统运行时,CPU执行计算的过程如下:
-
<李>程序以及数据被加载到主内存李>
<李>指令和数据被加载到CPU缓存李>
<李> CPU执行指令,把结果写到高速缓存李>
<李>高速缓存中的数据写回主内存李>
如果服务器是单CPU核,那么这些步骤不会有任何的问题,但是如果服务器是多核CPU,那么问题来了,以英特尔酷睿i7处理器的高速缓存概念模型为例(图片摘自《深入理解计算机系统》):
试想下面一种情况:
-
<李>核0读取了一个字节,根据局部性原理,它相邻的字节同样被被读入核0的缓存李>
<李>核3做了上面同样的工作,这样核0与核3的缓存拥有同样的数据李>
<李>核0修改了那个字节,被修改后,那个字节被写回核0的缓存,但是该信息并没有写回主存李>
<李>核3访问该字节,由于核0并未将数据写回主存,数据不同步李>
为了解决这个问题,CPU制造商制定了一个规则:。于是,在上面的情况下,核3发现自己的缓存中数据已无效,核0将立即把自己的数据写回主存,然后核3重新读取该数据。
可以看的出,缓存在多核CPU的使用情况下会有一些性能的缺失。
有了上面的理论基础,我们可以研究动荡的关键字到底是如何实现的。首先写一段简单的代码:
/* * * @author五月的仓颉http://www.cnblogs.com/xrq730/p/7048693.html */公开课LazySingleton { 私有静态稳定LazySingleton实例=零; 公共静态LazySingleton getInstance () { 如果(实例==null) { 实例=new LazySingleton (); } 返回实例; } 公共静态void main (String [] args) { LazySingleton.getInstance (); } }
首先反编译一下这段代码的. class文件,看一下生成的字节码: