从java中的 AtomicInteger去看cas

我们在之前的文章中介绍过原子性问题。当时我们说过使用synchnorized关键字就可以保证安全。今天我们看看java中为我们提供的另一种实现原子类。我们也AtomicInteger 为例去看看源码。

AtomicInteger

从java中的 AtomicInteger去看cas_第1张图片

先看这货的几个属性。unsafe是啥? valueOffset又是干什么的。我们放到后续再聊。从java中的 AtomicInteger去看cas_第2张图片

通过上面的代码我们清晰的知道这个value 就是存储这个对象当前的current Value。我们再看看下面的代码

从java中的 AtomicInteger去看cas_第3张图片

下面我们来看看这个AtomicInteger是如何实现原子性操作的,请看图

从java中的 AtomicInteger去看cas_第4张图片

又是cas操作。那什么是cas呢?它现在似乎已经是一座我们不解决就无法继续前进的路障。直接找到该方法在jvm中的实现位置在

/openjdk/hotspot/src/os_cpu/linux_x86/vm/下,我们可以通过逻辑简单的看出,其会根据是否为多核cpu来判断是否进行加锁。

最后会执行cmpxchg指令。

从java中的 AtomicInteger去看cas_第5张图片

总之我们可以得出一个结论就是cas操作可以说是java调用c ,通过c使用汇编指令调用cpu实现的操作。那我们下面就从理论的角度来看看为什么cas操作能保证原子性问题。

从java中的 AtomicInteger去看cas_第6张图片

前文已经描述过cpu缓存存在的来由是因为cpu的运算速度远远大于内存的处理速度。所以我们会存在多级缓存。每个cpu运算时数据都存在自己的缓存内,而为了保证原子性。cpu实现了和总线锁定内存锁定

总线锁定:就是使用处理器提供的一个#lock信号。当一个处理器在总线上输出次信号时,其他处理器请求将被阻塞。该处理器可以独占共享内存

缓存锁定:即如果共享变量被处理器缓存在缓存行中。当它执行锁操作回写到内存时,则不会在总线上输出#lock信号,而是直接修改内部存在的地址,通过mesi保证其他处理器内该共享变量不被同时修改。

通过上面的原理我们可以理解cas操作是如何实现原子操作了。那cas和我们的锁比较有哪些优势呢,首先cas锁是非阻塞的即使失败会使用自旋重复尝试。而synchronized 是阻塞的。并且在抢锁较少的环境下,消耗的资源比加锁少的多。

可是cas也有三个臭名昭著的问题

第一其只能保证一个共享变量的原子操作

第二自旋时间过长会造成cpu资源的大量消耗

第三aba问题 (其中java的 AtomicStampedReference 就是为了解决这个问题。)

今天的总结就到这里 欢迎大家指正问题 !!  与君共勉

 

你可能感兴趣的:(并发编程基础)