深入理解CAS和常用原子类

1.CAS(比较并交换、无锁机制、乐观锁)

CAS 比较并交换 Compare And Swap,它是一种线程安全的原子语义操作,他的工作流程是:
针对一个变量,首先对变量进行修改,然后比较它的内存值和期望值进行比较,如果相同则将修改后的新值覆盖内存,否则什么都不处理。CAS的比较和交换两个步骤通过CPU的一个汇编指令执行,具有原子性,通常CAS可以看做是一种乐观锁的机制。

1.1 CAS工作流程

在这里插入图片描述

1.2 CAS底层原理和内存语义

Java的CAS机制是通过unsafe类来进行API调用完成,例如对int变量的CAS操作,可以通过调用Unsafe#compareAndSwapInt()来完成,但是这是一个native本地方法,是JNI链接通过C++代码实现。
Hotspor虚拟机对compareAndSwapInt()的实现是通过调用Atomic::cmpxchg方法完成,这个cmpxchg在不同的操作系统和CPU架构模式下都不一样,Linux_X86架构下,CAS最底层就是通过cmpxchgl汇编指令来完成,但是因为CAS保证了原子性没有保证可见性,所以Hotspot在cmpxchgl前加入了LOCK_IF_MP判断是否为多核处理架构,如果是多核则在汇编指令前加入CPU的Lock前缀指令来保证可见性问题。所以Java的CAS机制既能保证原子性也能保证可见性。
在这里插入图片描述

1.3 CAS的缺陷

1.3.1 Unsafe中的CAS自旋方式缺陷

cmpxchgl汇编指令本身没有自旋的功能,JDK中原子类和unsafe类提供的CAS是有while自旋操作的,但是如果在高并发场景下对共享变量修改时,会让大量的线程修改失败转而进行自旋,此时CPU会因此大量的自旋从而CPU开销变大,CPU利用率降低。并且,JVM在多核架构下还会添加Lock前缀指令造成总线事务的攀升,总线事务嗅探也变得极为繁忙,总线带宽打满,进而造成总线风暴。

1.3.2 只能保证一个共享变量的原子操作缺陷

JDK提供了AtomicReference类来优化这个问题,但是并不是真正意义上保证多个变量,而是对对象进行CAS操作。

1.3.3 ABA问题

在这里插入图片描述

1.3.3.1 ABA解决方案

JDK提供了版本号机制AtomicStampedReference<V>类,来解决ABA问题,就是每次修改时更新版本号。
AtomicMarkableReference<V>类也能解决,只不过该类不关心版本变更了多少次,只关心是否发生了改变。

2.Java常用原子类

JDK的JUC包下的atomic子包下提供了许多的原子类,他们都是基于CAS机制实现的线程安全的变量计算的类。这些类答题可以分为五大类:

  - 基本类型原子类:AtomicInteger、AtomicLong、AtomicBoolean等
  - 引用类型原子类:AtomicReference、AtomicStampedRerence、AtomicMarkableRerence
  - 数组类型原子类:AtomicIntegerArray......等
  - 对象属性原子类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater等
  - 原子类型累加器(JDK8):LongAdder、DoubleAdder、LongAccumulator、Striped64等。

2.1 AtomicInteger对int类型的CAS操作

        - getAndIncrement() 获取原值,然后CAS增1。
        - getAndSet() 获取原值,然后CAS设置新值。
        - incrementAndGet() CAS增1,然后返回最新值。

2.2 AtomicIntegerArray对int[ ]的CAS操作

        - addAndGet(int index, int value) CAS形式对index位置的值变更为新值。
        - getAndIncrement(int index) CAS形式对index位置的值自增1。

2.3 AtomicReference对引用类型的CAS操作

        - compareAndSet(Object o1, Object o2) CAS方式将o1的引用赋值给o2。

3.LongAdder(优化高并发下CAS自旋瓶颈问题)

Java在JDK8版本引入了LongAdder、DoubleAdder来优化高并发环境下的CAS自旋导致的性能消耗问题,他的设计理念就是热点分散分而治之。不过LongAdder并不一定比AtomicLong等优秀,如果在低并发环境下,LongAdder着实有点杀鸡用牛刀的感觉,性能上甚至还不如用普通原子类。
在这里插入图片描述

3.1 LongAdder#add()方法

在这里插入图片描述

LongAdder在CAS没有发生并发修改失败的场景下,就是对base的线性操作。如果一旦发生了冲突,那么线程就会将在Cell[ ]内操作各自独立负责的单元。而Cell数组的初始化和扩容的问题交给父类Striped64#longAccumulate()方法完成。
LongAdder的使命就是热点分散

3.2 Striped64#longAccumulate()方法

该方法主要负责完成Cell[ ]的初始化工作和cell单元格CAS并发冲突时的解决、数组扩容的处理(2倍扩容)。

3.3 LongAdder#sum()方法

这个方法就是最后将Cell[ ]合并的方法,但是该方法并没有加锁处理,所以在高并发模式下,sum()方法的汇总统计结果可能会不准确,LongAdder只是近似准确的计数值。

3.4 LongAccumulator类

它是对LongAdder的增强,LongAdder只能针对数值进行加减运算,而LongAccumulator提供了自定义函数计算。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Minor王智

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值