- title: Java中CAS(Compare And Swap,比较和交换)算法的技术原理简述
- date: 2021/8/14
文章目录
CAS全称 Compare And Swap,是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
这里简要提一下乐观并发策略。
实现线程安全的方法主要有三大手段:阻塞同步(也叫互斥同步),非阻塞同步(基于冲突检测的乐观并发策略)以及无同步方案;
从解决问题的方式上,互斥同步属于一种悲观的并发策略,无论共享的数据是否会出现竞争,它都会进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁(锁消除)),这会导致用户态到核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等开销。
随着硬件指令集的发展,我们有了另一个选择:基于冲突检测的乐观并发策略,通俗地说就是不管风险,先进行操作,如果没有其它线程争用共享数据,那么操作就直接成功了;如果共享的数据的确被争用,产生了冲突,再进行其它的补偿措施,最常用的补偿措施是不断地重试,直到出现没有竞争的共享数据为止。这种乐观并发策略的实现不再需要将线程阻塞挂起,因此这种同步操作被称为非阻塞同步,使用这种措施的代码也常被称为无锁(Lock-Free)编程。
这里提到硬件指令集,是因为我们必须要求操作和冲突检测两个步骤具有原子性。那么靠什么实现原子性呢?这里如果使用互斥同步来保证就完全失去了意义了,所以我们只能靠硬件来实现这件事情,硬件保证某些从语义上看起来需要多次操作的行为可以只通过一条处理器指令还完成,这类指令常用的有:
- 测试并设置(Test-and-Set);
- 获取并增加(Fetch-and-Increment);
- 交换(Swap);
- 比较并交换(Compare-and-Swap,即CAS);
- 加载连接/条件储存(Load-Linked/Stored-Conditional,即LL/SC);
其中后两条指令是现代处理器新增的,其中在Java中最终暴露的是CAS操作。
CAS指令需要三个操作数:
- 需要读写的变量的内存位置 V;
- 旧的预期值 A;
- 准备设置的新值 B;
当且仅当 V 的值等于 A 的值时,CAS通过原子操作用新值 B 来更新V的值(“比较+更新”是一个原子操作,执行期间不会被其它线程打断),否则不会执行更新。一般情况下,“更新”是一个不断重试的操作。不管是否更新了V的值,都会返回V的旧值。
下面通过查看AtomicInteger的自增函数incrementAndGet()的源码,发现自增函数底层调用的是Unsafe.getAndAddInt()。但是JDK本身只有Unsafe.class,因此可以通过OpenJDK 8 来查看Unsafe的源码;
//AtomicInteger.java
private static final long valueOffset;</