java cas原理解析

本文内容是假定读者已经有一些多线程的工作经验以及一些计算机系统原理知识的前提下进行介绍的,所以可能并不是说的很通俗易懂,也仅是个人作为一个知识的总结分享。

相信大家在开发过程中对java的cas操作并不陌生,cas作为一种乐观锁的实现方式,我们在java.util.concurrent包下可以看到很多类都使用了cas的方式解决多线程并发问题:

java cas原理解析_第1张图片

可以看到这些api的底层都是调用了Unsafe的方法, 我们在跟进去就是native方法了:

基本上好多同学就被劝退了。也确实,只要JVM保证这个cas的语义。我们并不需要关心具体JVM如何实现,这也是很多系统间进行分层的原因,屏蔽底层的细节。我今天要讲的就是具体这个native方法又是如何实现的。

首先大家都知道cas操作底层是靠cpu来保证原子性的,cpu仅仅只在简单的操作上保证原子性,比如cpu读取内存值,写入内存值等。另外也提供了一些特殊的操作指令,并且保证他们的执行是原子性的,其中就有cmpxchg.翻译出来就是compare and swap:

java cas原理解析_第2张图片

 cas(expectvalue,newvalue)。将期望的值改为新值。这一点我们硬件层面有进行保证的原子指令。但是你仔细思考一下,如果仅仅是硬件指令就可以解决并发修改了吗?假设cas期间其他线程修改了值了。那不是当前的这次cas操作就失败了吗?还有就是ABA的问题,其他线程修改值后又改回原值了呢?大多数情况下ABA问题我们并不需要关心,但是特殊场景下,我们需要解决这种问题如何解决呢?JDK的源码给了思路那就是引入一个version版本号的机制来记录修改的次数,我们可以查看AtomicStampedReference源码:

java cas原理解析_第3张图片

封装了一个内部静态类Pair来包装引用和版本号,之所以这么设计的原因,其实主要是因为cas的操作目前只能针对一个值进行操作。而我们要解决版本号和引用是两个变量了。所以采用了这种合并的形式,来对pair对象进行cas操作:

java cas原理解析_第4张图片 

有点偏题了。这是解决ABA的问题。现在继续说一下JVM如何实现cas的API:

java cas原理解析_第5张图片 

java cas原理解析_第6张图片 

 java cas原理解析_第7张图片

 经过jvm的c++实现。最终底层其实主要是两个指令 lock  cmpxchg。那么这个lock指令是干嘛呢。其实我们语言层面说的乐观锁,其实在硬件层面在Lock指令这里属于悲观形式了。他会锁缓存或者系统总线并且无视中断直接指令完成。我们所有的内存数据与cpu交互都需要经过总线的。锁着总线就是让其他线程暂时无法访问只能有当前线程访问。锁缓存一个道理。只是粒度小一点。这就是cas底层硬件的实现方式。 

另外说明一个问题。就是我们进行i++测试的时候会使用AtomicInteger的类来进行计算。但是会存在一个循环cas的方法。当时我觉得如果其他线程改了值。那不是就会一直循环下去了吗?现在我们仔细看一下源码。其实并不是一直拿一个oldvalue替换成newValue的:

java cas原理解析_第8张图片

是每次都会读取当前的新值然后进行累加操作。这里需要当前值是volatile修饰才行! 

你可能感兴趣的