一、概述
1.1 CAS介绍
CAS全称是 Compare-And-Swap 意思为比较并替换,主要用于乐观锁
CAS分两步:
1、比较: 读取到了一个值 A,在将其更新为 B 之前,检查原值是否仍为 A
2、设置: 检查结果为真,则更新为B,否则一直重试,直到成功为止
深入理解 CAS 原理 | Java
1.2 CAS思路
在大多数处理器的指令中,都会实现 CAS 相关的指令,这一条指令就可以完成“比较并交换”的操作,也正是由于这是一条(而不是多条)CPU 指令,所以 CAS 相关的指令是具备原子性的,这个组合操作在执行期间不会被打断,这样就能保证并发安全。由于这个原子性是由 CPU 保证的,所以无需我们程序员来操心。
CAS 有三个操作数:内存值 V、预期值 A、要修改的值 B。CAS 最核心的思路就是,仅当预期值 A 和当前的内存值 V 相同时,才将内存值修改为 B。
1.3 CAS优缺点
CAS优点
可以避免加互斥锁,可以提高程序的运行效率
CAS缺点
ABA 问题
CAS 有一个典型的问题就是ABA问题,即原始值最初为a,但是中间被其它线程修改多次,最后又变为了a,当进行比较时,我们程序认为没有被其它线程修改。CAS 并不能检测出在此期间值是不是被修改过,它只能检查出现在的值和最初的值是不是一样。
ABA 问题对中间一定不能被其它线程操作的场景有影响,可以通过版本号的方式解决,因此,使用时需要注意是否需要规避ABA问题。
自旋时间过长
由于单次 CAS 不一定能执行成功,所以 CAS 往往是配合着循环来实现的,有的时候甚至是死循环,不停地进行重试,直到线程竞争不激烈的时候,才能修改成功。
可是如果我们的应用场景本身就是高并发的场景,就有可能导致 CAS 一直都操作不成功,这样的话,循环时间就会越来越长。而且在此期间,CPU 资源也是一直在被消耗的,这会对性能产生很大的影响。所以这就要求我们,要根据实际情况来选择是否使用 CAS,在高并发的场景下,通常 CAS 的效率是不高的。
范围不能灵活控制
不能针对多个共享变量同时进行 CAS 操作,因为这多个变量之间是独立的,简单的把原子操作组合到一起,并不具备原子性。因此如果我们想对多个对象同时进行 CAS 操作并想保证线程安全的话,是比较困难的
有一个解决方案,那就是利用一个新的类,来整合刚才这一组共享变量,这个新的类中的多个成员变量就是刚才的那多个共享变量,然后再利用 atomic 包中的 AtomicReference 来把这个新对象整体进行 CAS 操作,这样就可以保证线程安全。
相比之下,如果我们使用其他的线程安全技术,那么调整线程安全的范围就可能变得非常容易,比如我们用 synchronized 关键字时,如果想把更多的代码加锁,那么只需要把更多的代码放到同步代码块里面就可以了
二、CAS使用
2.1 原子类的CAS
java的原子类位于java.util.concurrent.atomic包下
AtomicInteger 使用了CAS,例如getAndAdd方法
代码语言:java复制public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
代码语言:java复制public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 var4));
return var5;
}
2.2 并发容器的CAS
ConcurrentHashMap 的putVal中使用了casTabAt方法
代码语言:java复制final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//以下部分省略
...
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) ABASE, c, v);
}
}
2.3 数据库的CAS
在我们的数据库中,也存在对乐观锁和 CAS 思想的应用。在更新数据时,我们可以利用 version 字段在数据库中实现乐观锁和 CAS 操作,而在获取和修改数据时都不需要加悲观锁。
具体思路如下:当我们获取完数据,并计算完毕,准备更新数据时,会检查现在的版本号与之前获取数据时的版本号是否一致,如果一致就说明在计算期间数据没有被更新过,可以直接更新本次数据;如果版本号不一致,则说明计算期间已经有其他线程修改过这个数据了,那就可以选择重新获取数据,重新计算,然后再次尝试更新数据。
假设取出数据的时候 version 版本为 1,相应的 SQL 语句示例如下所示:
代码语言:sql复制UPDATE order SET name = 'aaa' Where version = 2
三、原子类
3.1 原子类作用
原子性意味着“要么全部成功,要么全部失败” java的原子类位于java.util.concurrent.atomic包下,作用和锁类似,和锁相比优势有:
粒度更细:
原子变量可以把竞争范围缩小到变量级别,通常情况下,锁的粒度都要大于原子变量的粒度。
效率更高:
除了高度竞争的情况之外,使用原子类的效率通常会比使用同步互斥锁的效率更高,因为原子类底层利用了 CAS 操作,不会阻塞线程。
3.2 原子类详述
原子类包含:
类型 | 具体类 |
---|---|
Atomic* 基本类型原子类 | AtomicInteger、AtomicLong、AtomicBoolean |
Atomic*Array 数组类型原子类 | AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray |
Atomic*Reference 引用类型原子类 | AtomicReference、AtomicStampedReference、AtomicMarkableReference |
Atomic*FieldUpdater 升级类型原子类 | AtomicIntegerfieldupdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater |
Adder 累加器 | LongAdder、DoubleAdder |
Accumulator 积累器 | LongAccumulator、DoubleAccumulator |
3.2.1 Atomic基本类型原子类
包含AtomicInteger、AtomicLong、AtomicBoolean
以AtomicInteger为例子,它是对于 int 类型的封装,并且提供了原子性的访问和更新。也就是说,我们如果需要一个整型的变量,并且这个变量会被运用在并发场景之下,我们可以不用基本类型 int,也不使用包装类型 Integer,而是直接使用 AtomicInteger,这样一来就自动具备了原子能力,使用起来非常方便。
1、源码
AtomicInteger源码
代码语言:java复制public class AtomicInteger extends Number implements java.io.Serializable {
/*
* This class intended to be implemented using VarHandles, but there
* are unresolved cyclic startup dependencies.
*/
private static final Unsafe U = Unsafe.getUnsafe();
private static final long VALUE
= U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value;
//获取当前的值
public final int get() {
return value;
}
//设置值
public final void set(int newValue) {
value = newValue;
}
//获取当前值,并设置新值
public final int getAndSet(int newValue) {
return U.getAndSetInt(this, VALUE, newValue);
}
//如果输入的数值等于预期值,则以原子方式将该值更新为输入值(update)
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
//获取当前的值,并自增
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
//获取当前的值,并自减
public final int getAndDecrement() {
return U.getAndAddInt(this, VALUE, -1);
}
//获取当前的值,并加上预期的值
public final int getAndAdd(int delta) {
return U.getAndAddInt(this, VALUE, delta);
}
Unsafe类中部分代码
代码语言:java复制@IntrinsicCandidate
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, newValue));
return v;
}
2、使用例子
代码语言:java复制 @Test
public void AtomicIntegerDemo() {
AtomicInteger atomicInteger = new AtomicInteger(0);
ExecutorService executorService = Executors.newFixedThreadPool(8);
for (int i = 1; i <= 100; i ) {
executorService.execute(() -> {
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.get());
});
}
System.out.println("final:" atomicInteger.get());
}