CAS算法_比较并交换
前言
Github:https://github.com/HealerJean
1、CAS
算法
在java中锁分为乐观锁和悲观锁。
1、悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。
2、乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。
CAS是
compare and swap
的缩写,即我们所说的比较交换。CAS
是一种基于锁的操作,(又称为无锁操作)是一种乐观锁策略。
1.1、原理
CAS 操作包含三个操作数 —— 内存位置(V
)、预期值(A
)和新值(B
)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS
是通过无限循环来获取数据的,当多个线程使用CAS
操作一个变量是,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试(我们自己设置的哦),直到没有冲突为止,当然也可以选择跳过或者,或者挂起线程
1.1.1、AtomicLong
原子性操作类
1.1.1.1、不使用synchronize
,使用原子性操作类AtomicLong
public class _1_AtomicMethod {
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(() -> {
if (Counter.addOne() == 1000) {
System.out.println("counter = 1000");
}
});
thread.start();
}
}
}
class Counter {
private static AtomicLong atomicLong = new AtomicLong(0);
public static long addOne() {
return atomicLong.incrementAndGet();
}
}
1.1.1.2、atomicLong.incrementAndGet
方法解析
incrementAndGet
调用getAndAddLong
方法
getAndAddInt
方法解析:拿到内存位置的最新值v
,使用CAS
尝试修将内存位置的值修改为目标值v + delta
,如果修改失败,则获取该内存位置的新值v
,然后继续尝试,直至修改成功。
value
:使用volatile
修饰保证了可见性
private volatile long value;
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
getAndAddLong
调用compareAndSwapLong
,即本文的主角CAS
unsafe.compareAndSwapInt(this, valueOffset, expect, update); CAS(Compare And Swap)
,意思是如果valueOffset
位置包含的值与expect
值相同,则更新valueOffset
位置的值为update
,并返回true
,否则不更新,返回false。
public final long getAndAddLong(Object o, long offset, long delta) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, v + delta));
return v;
}
1.2、CAS
问题
1.2.1、ABA
问题
因为CAS
会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,所以继续执行了,但是实际上的确发生了变化。
不容易看出问题的主要还是因为:“值是一样的”等同于“没有发生变化”(就算被改回去了,那也是变化)的认知。毕竟在大多数程序代码中,我们只需要知道值是不是一样的,并不关心它在之前的过程中有没有发生变化;所以,当我需要知道之前的过程中“有没有发生变化”的时候,ABA就是问题了。
解决方案:可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C。 1A——2B——3A 即使数据仍然是A,但是最后一步A的版本号为3,1A≠3A,因此可以避免ABA的问题
1.2.2、自旋时间过长
使用
CAS
时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。
1.2.3、只能保证一个共享变量的原子操作
当对一个共享变量执行操作时CAS
能保证其原子性,如果对多个共享变量进行操作,CAS
就不能保证其原子性。有一个解决方案是利用对象整合多个共享变量,即一个类中的成员变量就是这几个共享变量。然后将这个对象做CAS操作就可以保证其原子性。atomic中提供了AtomicReference
来保证引用对象之间的原子性。