CAS与原子类操作

CAS

什么是CAS

CAS的全称是Compare-And-Swap,直译就是对比与交换。CAS是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。

简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换

CAS使用举例

在并发线程操作中,比如并发线程累加操作同一个变量500次,但最终的结果一般都到不了500,原因是并发状态下的线程不安全问题

针对这个问题,我们可以将共享的操作变量int换成换成AtomicInteger,这样就能解决问题,其中AtomicInteger实现是使用了CAS操作

CAS问题

CAS方式为乐观锁,synchronized为悲观锁。因此使用CAS解决并发问题通常情况下性能更优

但是CAS仍然存在三大问题:ABA问题、循环开销大、只能保证一个共享变量的原子操作

ABA问题

什么是ABA问题:因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,中途变成了B,后来又变成A,那么使用CAS进行检查时会发现它的值没有发生变化,而实际却发生过变化。在大多数的业务场景ABA问题是可以容忍的,但是部分业务就会带来问题。

如:小明去银行取款,银行账户有100元,小明准备取50出来,由于机器不灵,小明多点击了几次取款操作,触发了后台线程A和线程B的工作,此时线程A操作成功(100->50),线程B阻塞。此时正好小明收到了50元的转账且执行成功了(50->100),之后线程B执行了,又执行成功(100->50)。最终小明账户只剩下50元,等于被错误的扣了两次款。

ABA问题的解决思路:使用版本号。再变量前面追加上版本号,每次变量更新的时候吧版本号+1,那么A->B->A就会变成1A->2B->3A。

从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设为给定的更新值

循环时间长,开销大

自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销

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

当对一个共享变量执行操作时,我们可以使用循环CAS的方式保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可以用锁

 

原子操作类

原子更新基本类型类

使用原子的方法更新基本类型,Atomic包提供以下3个类

  • AtomicBoolean:原子更新布尔类型
  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新长整型

以上三个类提供的方法几乎一模一样,下面以AtomicInteger为例

  • public final int get():获取当前的值
  • public final int getAndSet(int newValue):获取当前的值,并设置新的值
  • public final int getAndIncrement():获取当前的值,并自增1
  • public final int getAndDecrement():获取当前的值,并自减1
  • public final int getAndAdd(int delta):获取当前的值,并加上预期的值
  • void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能会导致其他线程在之后的一段时间内还是可以读到旧值

源码解析:AtomicInteger:getAndIncrement():

 public class AtomicInteger extends Number implements java.io.Serializable {​
private static final long serialVersionUID = 6214790243416807050L;​
​
private static final Unsafe U = Unsafe.getUnsafe();​
private static final long VALUE​
= U.objectFieldOffset(AtomicInteger.class, "value");​
​
private volatile int value;​
​
}​
​
public final int getAndIncrement() {​
return U.getAndAddInt(this, VALUE, 1);​
}​
​
public final int getAndAddInt(Object o, long offset, int delta) {​
int v;​
do {​
v = getIntVolatile(o, offset);​
} while (!weakCompareAndSetInt(o, offset, v, v + delta));​
return v;​
}​
​
public final boolean weakCompareAndSetInt(Object o, long offset,​
int expected,​
int x) {​
return compareAndSetInt(o, offset, expected, x);​
}

分析:可以看到,getAndIncrement操作的自增1操作,最终还是调用的unsafe类提供的compareAndSetInt(CAS)实现的

 

原子更新数组

通过原子的方式更新数组里的某个元素,Atomic包提供了以下的3个类:

  • AtomicIntegerArray:原子更新整型数组里的元素
  • AtomicLongArray:原子更新长整型数组里的元素
  • AtomicReferenceArray:原子更新引用类型数组里的元素

这三个类的最常用方法是如下两个方法:

get(int index):获取索引为index1的元素值

compareAndSet(int i,E except,E update):如果当前值等于预期值,则以原子方式将数组位置 i 的元素设为update值

原子更新引用类型

Atomic包提供了以下三个类:

  • AtomicReference:原子更新引用类型
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
  • AtomicMarkableReference:原子更新带有标记位的引用类型

这三个类提供的方法都差不多,首先构造一个引用对象,然后把引用对象set进Atomic类,然后调用compareAndSet等一些方法去进行原子操作,原理都是基于Unsafe实现,但AtomicReferenceFieldUpdater略有不同,更新的字段必须用volatile修饰

原子更新字段类

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下三个类进行原子字段更新

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值和引用关联起来,可用于原子地更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题

要想原子地更新字段需要两部,第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二部,更新类的字段(属性)必须使用public volatile修饰符。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇