摘要:本文学习了CAS相关的原理和操作。
环境
Windows 10 企业版 LTSC 21H2
Java 1.8
1 背景
1.1 线程安全
在并发编程中很容易出现并发安全的问题,比如多个线程执行i++
操作。
最常用的方法是通过加锁操作,但是由于加锁操作采用的是悲观锁策略,并不是特别高效的一种解决方案。
除此之外,也可以使用java.util.concurrent.atomic
包提供的原子类,使用CAS采用类似乐观锁的策略去更新数据,解决并发安全问题。
1.1 乐观锁和悲观锁
悲观锁假设对共享资源的访问都会发生冲突,当有一个线程访问资源,其他线程就必须等待,所以采用了加锁的方式解决冲突。
乐观锁假设对共享资源的访问是没有冲突的,线程可以同时访问资源。如果遇到冲突的话,就使用CAS技术重复检测冲突,直到没有冲突才能访问资源。
2 简介
CAS(Compare and Swap,比较并交换)技术属于乐观锁技术(又称为无锁技术),指当多个线程同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试更新。
3 原理
CAS包含了三个参数:
- V:表示要读写的内存位置。
- A:表示旧的预期值。
- B:表示新值。
在更新时比较V中存储的值和A的值:
- 如果二者不同,说明可能是其他线程做了更新,那么当前线程什么都不做,执行返回或者重试。
- 如果二者相同,将V中存储的值设为B的值,并返回A的值。
当多个线程同时使用CAS更新变量时,只有一个线程会成功,其余线程均会失败,但失败的线程不会被挂起,而是不断的再次循环重试,这个过程也称为自旋。
正是基于这样的原理,CAS即时没有使用锁,也能发现其他线程对当前线程的干扰,从而进行及时的处理。而且因为没有使用锁,也就不会造成死锁。
4 核心
Unsafe类是特殊的类,位于sun.misc
包中,其内部方法可以像C的指针一样直接操作内存,CAS依赖Unsafe类的方法。
Unsafe类是CAS的核心类,基于该类可以直接操作特定的内存数据,该类的所有方法都是native修饰的,并且该类提供了底层的CAS机制。
5 缺点
5.1 CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
解决办法目前没有,只能通过让JVM支持处理器提供的PAUSE指令来减轻CPU的压力。
5.2 多个共享变量不能保证原子性
CAS所保证的只是一个变量的原子性操作,而不能保证多个共享变量的原子性。
解决办法是使用锁机制或者使用AtomicReference类封装多个共享变量。
5.3 ABA问题
CAS判断变量操作成功的条件是V中存储的值和A的值是一致的,如果V中存储的值原来是A,被改成了B,又被改回为A,经过了两次的修改,但是CAS还是认为该变量从来没被修改过。
解决办法是使用AtomicStampedReference类通过追加版本号或时间戳同时判断原值是否改变。
条