前言
这篇文章躺在草稿里 6 个月了,最近才发现 2333,于是翻出来补全了下。
CAS
**CAS(Compare-and-Swap 比较和替换)**故名思意就是先比较然后替换的操作方式,是原子操作中的一种,同时也是无锁操作中的一种。在 Java 中广泛使用,常见的如 AQS 的更新状态、CopyOnWriteArrayList
。
通常情况下,我们操作某个共享资源,为了防止其他进程的干扰,我们通常会给这个共享资源加上一把锁,然而加锁,释放锁,竞争锁,线程的阻塞和唤醒是非常耗能的操作。而 CAS 采用的是记录内存位置和原始值,当记录的原始值与当前内存位置的值(当前值)相同的时候,就将新值更新到内存。如果不同说明在这过程中有其他线程干扰,执行失败策略。由于是无锁的操作,所以便没有了加锁和释放锁间一系列的额外开销,所以非常快。
流程
在进行 CAS 操作前需要三个操作参数:
- 内存位置 V(它的值是我们想要去更新的)
- 预期原值 A(上一次从内存中读取的值)
- 新值 B(应该写入的新值)
操作过程:
先从 V 中取值,与 A 比较,如果相等则将 V 中的值更新成 B,如果不相等,说明 V 上的值被修改过了,也就是其他线程干扰了,这时候就不更新,进入失败的操作流程,比如重试,报错等。
缺点
当然如果 CAS 真的这么好用的话 Java 中早就放弃锁了。CAS 虽然很高效的解决原子操作,但是 CAS 仍然存在三大问题,同时也有使用场景的限制。
ABA 问题
原因:由于 CAS 是通过比较来判断是否进行更新操作,如果一个值从 A 变成 B,然后又被改成 A,在 CAS 检查时会认为值是没变的,实际上是变化了。
解决方法:为每次操作都加上版本号,如果版本号有变,即使值没变也说明是有变化的。
循环时间长开销大
原因:如果自旋长时间不成功,则会给 CPU 带来很大的执行开销。
解决方法:利用 pause 指令提升自旋性能。
只能保证一个值的原子操作
原因:如果有多个值需要同时操作,那么用 CAS 就无法简单解决了。
解决方案:可以对值进行合并,比如封装成一个对象等等。
使用场景限制
从 CAS 流程中我们可以看到,为了记录原始值,我们需要复制一份值,而这复制过程消耗的性能在少量的情况下并不明显,相比于锁更优。然而如果大量的执行 CAS 操作,或者操作的内容是比较庞大的,那么这些性能损耗就不能忽视了,较为极端的情况下甚至比锁还要差上很多。
AQS
AQS(AbstractQueuedSynchronizer 抽象排队同步器),是一个用来构建锁和同步器的框架,使用 AQS 能方便的构建出不同使用场景的同步器,常见的如 ReentrantLock
、CountDownLatch
。AQS 采用模板方法模式,实现大量通用方法,子类通过继承方式实现其抽象方法来管理同步状态。
流程
当某个线程从 AQS 获取共享资源的时候,如果被请求的共享资源空闲,那么就将该线程设置为工作线程,然后锁定共享资源。如果被请求的共享资源被占用,那么就将线程加入 CLH 队列中,同时阻塞线程,进行排队等待。
CLH 队列
CLH 队列是一个虚拟的 FIFO 的双向队列。头节点是一个获取同步状态成功的节点。线程通过 AQS 获取锁失败,就会将线程封装成一个 Node 节点,插入队列尾。当有线程释放锁时,后唤醒头节点的 next 节点(第二个节点)尝试占用锁。
同步状态
在 AQS 中有一个 state
字段用于表示同步状态,使用了 volatile
关键字修饰,实现类可以通过 CAS 方式来更新同步状态。
资源共享模式
AQS 有两种资源共享模式:
- 独占(Exclusive):即同一时间内只有一个线程能够获取共享资源。常见的实现如
ReentrantLock
。 - 共享(Share):即一个共享资源可被多个线程同时使用。常见的实现如
CountDownLatch
。
AQS 为这两种资源共享模式提供了两组不同的方法,如独占式的 acquire
、release
,共享式的 acquireShared
、releaseShared
。按场景重写不同模式的方法即可实现不同的资源共享模式。当然也可以同时使用,比如 ReentrantReadWriteLock
就实现了独占和共享两种模式。
阻塞与唤醒
当有线程获取到共享资源了,其他线程获取时需要阻塞,当线程释放共享资源后,AQS 会负责唤醒在排队的线程。
AQS 通过 LockSupport
是用来创建锁和其他同步类的线程阻塞工具类,可以让线程在任意位置阻塞。每个使用 LockSupport
的线程都会与一个许可关联,如果该许可可用,则调用 park()
方法将会立即返回(加锁),否则会阻塞。如果许可尚不可用,则可以调用 unpark
使其可用(释放锁)。类似于 Object
类的 wait
和 notify
。
结语
ReentrantLock 相关的东西就暂时不再这篇文章写了,打算放到下一篇。
浅谈并发:CAS & AQS
https://blog.ixk.me/post/talking-about-concurrent-cas-and-aqs许可协议
BY-NC-SA
本文作者
Otstar Lin
发布于
2021/02/13
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!