自身volatile的底层实现原理的理解

概念:
volatile关键字,是一个变量在多个线程间可见。

比如说A,B,线程共享一个变量,java默认是A线程中保留一份copy,
这样如果B 线程修改了该变量,则A线程未必能看的见。使用该关键字就可以使所有线程
能够读到变量修改的值。

比如下面的小例子,running变量存在jvm堆内存的t对象中,当线程t1开始工作的时候,会把running变量从堆内存中读取到线程t1工作区,在运行过程中使用这个copy
不会每次到堆内存去读取。这样的话,其它线程(B线程)修改了该变量的值,该线程看不到,会始终运行。

使用volatile该关键字,将会强制所有的线程去堆内存中读取running的值。
自身volatile的底层实现原理的理解_第1张图片
下面看一下有无volatile的值的运行结果:
无volatile关键字:
自身volatile的底层实现原理的理解_第2张图片
有volatile关键字:
自身volatile的底层实现原理的理解_第3张图片
volatile的作用:(面试会问到)
1.保证线程可见性

所有线程都是共享堆内存的,除了共享堆内存之外,每个线程都有自己独立工作内存(线程栈),

如果在共享内存里有这么一个值的话,当一个线程想要去访问这个值的时候,会将这个值copy

一份,copy到自己的工作内存,对这个值的改变首先是在自己的工作内存进行改变,改完之后

,马上写回去。在这个线程里发生的改变,并没有及时的反映到另一个线程里面,这就是线程不

可见。加了volatile,当这个线程对这个变量做了修改的时候,另一个线程马上能看到。

2.禁止指令重排序:

它和cpu有关系,每次写都会被线程读到。加了volatile,cpu原来执行指令的时候它是一步

的执行,但是现在CPU为了提高效率,会把指令并发执行,第一个指令执行一半的时候,第二个

指令就已经开始执行了,这叫流水线式的执行。在这种新的架构设计的基础之上想充分利用这

一点,那么要求你的编译器把你的源码编译完的指令之后呢可能进行一个指令的重排序。

这就是关键字的作用。

那么接下来我说一下DCL双重检查锁单例需不需要加volatile:

肯定要加,不加可能会产生对象半初始化状态,那么什么是半初始化对象呢?看下图的分析

自身volatile的底层实现原理的理解_第4张图片
使用了半初始化态的对象,第一个线程来访问的时候,判断不为空,直接使用了int m=8,第二个线程再来访问的时候,已经int m=0;
看下面多个线程并发访问的时候的代码:
自身volatile的底层实现原理的理解_第5张图片
它可能会出现啥情况呢,不加volatile不会有太大的问题,因为synchronized已经保证了同步,但是不加volatile会出现指令重排序,对象半初始化状态。但是在运行代码的时候看不出来有任何问题

说完半初始化状态的对象,那么我们就分析一下volatile在底层到底如何实现的呢?

五层实现volitile:
1.java的源码:volatile int i;
2.ByteCode字节码(ACC_VOLATILE:Access flags:0x004a标识了volatile)
3.jvm虚拟机规范(内存屏障)
4.HotSpot实现(c++汇编语言的调用:lock cmpxchg )
5.CPU级别(缓存一致性协议):modified(修改),Exclusive(独占),shared(共享),
invalid(失效)

第一种我们在上面已经说过了,加了volatile与不加volatile的区别,接下来我们看第二种是怎么实现的呢?看下图的字节码文件
自身volatile的底层实现原理的理解_第6张图片
有好多编译器,大家可以在idea上下载编译器,查看字节码,这里我用的是一个软件。字节码层级的实现。

3.jvm内存屏障的实现:
屏障两边的指令不可以重排,保证有序性:
LoadLoad
StoreStore
LoadStore
StoreLoad
在StoreStoreBarrier之间 在LoadLoadBarrier之间
volatile写操作 volatile读操作
StoreLoadBarrier LoadStoreBarrier

4.在HotSpot中的实现,直接上代码:
自身volatile的底层实现原理的理解_第7张图片
这是在c++代码中的实现,最终会调用fence()方法实现。

5.在CPU层级上的实现:
首先我们得理解缓存行的概念:
缓存行对齐:

缓存行64个字节是CPU同步的基本单位,缓存行隔离会比伪共享效率要高,CacheLine:每个缓存

行有64个字节(64byte),缓存一致性协议来保证缓存行的线程可见性(硬件上用缓存锁实现)lock

上的指令来实现。

那么我们先了解下计算机CPU的知识,看如下图:
自身volatile的底层实现原理的理解_第8张图片
首先,当我们计算机通电的时候,我们通过I/O流将磁盘的数据读取到内存中,然后再由CPU的pc程序计数器,去读取每一条指令,放到Regesiter寄存器中,通过ALU来计算寄存器的指令,然后写回到内存。然而这样做的效率会加大IO的开销,每一次读取,我们都要从内存中去读,然后执行完指令再写回去,再读再写,一次次会加大资源的开销,成本高。那么是如何解决这些个问题的呢?
CPU专门提供了一个缓存行,当我们从内存中将数据读过来的时候,首先读到缓存行中,然后pc程序计数器,直接从缓存中去读数据,这样不仅效率高,而且成本低,下面给大家看一副图,缓存行内容:

分为:寄存器,L1高速缓存,L2高速缓存,L3高速缓存,L4(主存),
L5:磁盘,L6计算机的文件。这样的话,寄存器的效率最高。
自身volatile的底层实现原理的理解_第9张图片
这样的话,会从缓存中一级一级的去读取,先从主存中读取到L3缓存行中,再读到L2缓存行中,再读到L1中,再到寄存器中,通过ALU计算。下面我们再看个小例子,证明
在CPU层级数据一致性是以缓存行为单位的,并且缓存行隔离要比伪共享效率高:
自身volatile的底层实现原理的理解_第10张图片
自身volatile的底层实现原理的理解_第11张图片
在这里插入图片描述
我们可以多运行几次这个小程序,可以观察它的执行时间,基本保持在八十到100之间。从而证明了我们的问题。
这在CPU层级上实现了缓存一致性协议。

这就是我最近对一个volatile的认识,还有不足的地方,希望指正。

你可能感兴趣的:(volatile)