CAS(Compare-and-Swap,比较并交换)作为一种无锁(乐观锁)的原子操作,在并发编程中有着广泛的应用。以下是 CAS 的一些主要使用场景
一、 实现无锁数据结构(Lock-Free Data Structures)
这是 CAS 最重要的应用场景之一。无锁数据结构不使用传统的互斥锁(例如 synchronized
或 ReentrantLock
)来实现线程安全,而是依赖于 CAS 等原子操作。
-
1. 无锁队列(Lock-Free Queue):
- 应用场景: 高并发、低延迟的消息队列、任务队列等。
- 原理: 使用 CAS 操作来原子性地更新队列的头指针、尾指针等关键数据,避免了多个线程同时修改队列结构时的数据竞争。
- 优势: 避免了锁带来的死锁和上下文切换开销,在高并发场景下通常具有更好的性能。
- 典型实现:
- Java 并发包中的
ConcurrentLinkedQueue
(单向链表实现的无锁队列)。 - Disruptor(高性能无锁队列,环形缓冲区结构)。
- Mpsc Queue in Netty
- Java 并发包中的
-
2. 无锁栈(Lock-Free Stack):
- 应用场景: 需要线程安全地进行栈操作的场景,例如线程池中的任务栈。
- 原理: 使用 CAS 操作来原子性地更新栈顶指针。
- 典型实现:
- Java 并发包中的
ConcurrentLinkedDeque
(双端队列,也可以用作栈)。
- Java 并发包中的
- 代码示例:(简化)
public class LockFreeStack<T> { private AtomicReference<Node<T>> top = new AtomicReference<>(); public void push(T value) { Node<T> newHead = new Node<>(value); Node<T> oldHead; do { oldHead = top.get(); newHead.next = oldHead; } while (!top.compareAndSet(oldHead, newHead)); } public T pop() { Node<T> oldHead; Node<T> newHead; do { oldHead = top.get(); if (oldHead == null) { return null; // 栈为空 } newHead = oldHead.next; } while (!top.compareAndSet(oldHead, newHead)); return oldHead.value; } private static class Node<T> { final T value; Node<T> next; Node(T value) { this.value = value; } } }
-
3. 无锁哈希表(Lock-Free HashMap):
- 应用场景: 高并发的键值对存储,例如缓存系统。
- 原理: 使用 CAS 操作来原子性地更新哈希表中的桶(bucket)或节点,处理冲突和扩容等操作。
- 典型实现:
- Java 并发包中的
ConcurrentHashMap
(虽然ConcurrentHashMap
在某些操作上使用了锁,但在很多情况下,它使用 CAS 来实现无锁操作,例如putIfAbsent
,replace
等)。 - Cliff Click 实现的高性能
NonBlockingHashMap
(非 JDK 标准,性能极高)。
- Java 并发包中的
二、 实现原子类 (Atomic Classes)
Java 并发包 (java.util.concurrent.atomic
) 提供了一系列原子类,它们内部使用 CAS 来实现原子操作,为开发者提供了方便、高效的线程安全工具。
-
1.
AtomicInteger
,AtomicLong
,AtomicBoolean
:- 应用场景: 线程安全的计数器、序列号生成器、标志位等。
- 原理: 内部使用
volatile
变量和 CAS 操作来保证原子性。 - 示例: 高并发场景下的请求计数、ID 生成。
-
2.
AtomicReference
:- 应用场景: 需要原子性地更新对象引用的场景。
- 原理: 使用 CAS 操作来原子性地更新对象引用。
- 示例: 实现无锁的单例模式、无锁的配置更新。
-
3.
AtomicStampedReference
,AtomicMarkableReference
:- 应用场景: 解决 CAS 的 ABA 问题。
- 原理:
AtomicStampedReference
: 内部维护了一个 “版本号” (stamp),每次 CAS 操作都会比较并更新版本号。AtomicMarkableReference
: 内部维护了一个 “标记位” (boolean),用于表示对象是否被修改过。
- 示例: 对需要严格保证数据一致性的场景,例如银行账户余额更新。
-
4.
AtomicIntegerArray
,AtomicLongArray
,AtomicReferenceArray
:- 应用场景: 需要原子性地更新数组中元素的场景。
- 原理: 使用 CAS 操作来原子性地更新数组中的指定元素。
三、 实现乐观锁 (Optimistic Locking)
乐观锁是一种并发控制机制,它假设在更新数据时不会发生冲突,因此不会加锁。 在更新数据时,会检查数据是否被其他线程修改过,如果没有被修改过,则更新成功;如果被修改过,则更新失败,需要重试或回滚。
-
应用场景:
- 数据库操作: 在更新数据库记录时,使用版本号或时间戳来判断数据是否被其他事务修改过。
- Web 应用中的并发更新: 例如,多个用户同时编辑同一个文档,可以使用乐观锁来避免数据冲突。
-
原理:
- 读取数据时,同时读取数据的版本号(或时间戳)。
- 修改数据。
- 更新数据时,比较当前版本号与读取时的版本号是否一致。
- 如果一致,说明数据没有被其他线程修改过,执行更新操作,并更新版本号。
- 如果不一致,说明数据已经被其他线程修改过,更新失败,需要重试或回滚。
-
CAS 可以用来实现乐观锁中的 “检查并更新” 操作。
四、 实现同步器 (Synchronizers)
Java 并发包中的一些同步器,例如 AbstractQueuedSynchronizer
(AQS),也使用了 CAS 来实现底层的同步机制。 AQS 是许多并发工具类(例如 ReentrantLock
, Semaphore
, CountDownLatch
等)的基础。
-
应用场景:
- 实现自定义的同步器。
- 理解 Java 并发包中各种同步器的工作原理。
-
原理:
- AQS 内部维护了一个
volatile int state
变量,表示同步状态。 - AQS 使用 CAS 操作来原子性地更新
state
变量,实现锁的获取和释放、线程的等待和唤醒等操作。
- AQS 内部维护了一个
五、 其他应用场景
- 高性能计数器: 在高并发场景下,使用 CAS 实现的计数器通常比使用锁的计数器性能更好。
- 并发算法: CAS 是许多并发算法的基础,例如 Michael-Scott 队列、Herlihy-Shavit 栈等。
- 无锁编程 (Lock-Free Programming): CAS 是实现无锁编程的关键技术,可以避免死锁,提高并发性能。
总结
CAS 的应用场景非常广泛,主要可以归纳为以下几类:
- 无锁数据结构: 构建高性能、线程安全的无锁数据结构,例如队列、栈、哈希表等。
- 原子类: Java 并发包中的原子类,提供原子性的基本类型操作和引用操作。
- 乐观锁: 实现乐观锁机制,避免数据库或 Web 应用中的并发更新冲突。
- 同步器: 构建自定义同步器,或理解 Java 并发包中同步器的工作原理。
- 高性能计数器和并发算法: 在需要高性能和并发控制的场景中,使用 CAS 实现特定的算法。
理解 CAS 的原理和适用场景,可以帮助你更好地进行并发编程,编写出更高效、更可靠的多线程应用程序。 但需要注意 CAS 的 ABA 问题、循环开销等问题,并根据实际情况选择合适的解决方案。