CAS(Compare And Swap,比较并交换)是一种无锁的并发控制机制,广泛应用于多线程编程中。它通过原子操作确保数据的一致性和安全性,与传统的锁机制相比具有许多优点,但也存在一些缺点。本文将详细讨论 CAS 的优点和缺点,并结合代码示例和实际应用场景进行分析。
CAS 的优点
1. 无锁机制
CAS 是一种无锁机制,避免了传统锁带来的死锁、线程阻塞和上下文切换等问题。
示例:使用 CAS 实现无锁计数器
java
import java.util.concurrent.atomic.AtomicInteger;
public class CASCounter {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
public int get() {
return counter.get();
}
public static void main(String[] args) {
CASCounter casCounter = new CASCounter();
casCounter.increment();
System.out.println("Counter: " + casCounter.get());
}
}
2. 高性能
由于 CAS 操作是原子的,且通常由硬件直接支持,因此性能较高,尤其在高并发场景下表现更优。
性能对比:CAS vs 锁
java
import java.util.concurrent.locks.ReentrantLock;
public class LockCounter {
private int counter = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public int get() {
return counter;
}
}
// 测试代码可以比较两种计数器在高并发情况下的性能
3. 避免死锁和其他锁相关问题
CAS 不会引起死锁、优先级反转和线程饥饿等问题,因为它不需要显式的锁定和解锁操作。
示例:避免死锁
java
public void transfer(Account from, Account to, int amount) {
while (true) {
int oldFrom = from.getBalance();
int oldTo = to.getBalance();
if (oldFrom < amount) {
throw new IllegalArgumentException("Insufficient funds");
}
if (from.compareAndSetBalance(oldFrom, oldFrom - amount) &&
to.compareAndSetBalance(oldTo, oldTo + amount)) {
break;
}
}
}
CAS 的缺点
1. ABA 问题
ABA 问题是 CAS 算法最常见的问题之一。例如,一个变量从 A 变为 B 再变回 A,此时 CAS 操作会误认为它从未被修改过。
解决 ABA 问题
java
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABAProblemSolution {
private AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);
public void update(int newValue) {
int[] stampHolder = new int[1];
Integer current = atomicStampedRef.get(stampHolder);
int newStamp = stampHolder[0] + 1;
atomicStampedRef.compareAndSet(current, newValue, stampHolder[0], newStamp);
}
}
2. 自旋开销
CAS 操作通常通过自旋实现,即不断重试直到成功。如果竞争激烈,自旋时间可能很长,导致 CPU 资源浪费。
示例:自旋等待
java
public class SpinLock {
private AtomicBoolean lock = new AtomicBoolean(false);
public void lock() {
while (!lock.compareAndSet(false, true)) {
// 自旋等待
}
}
public void unlock() {
lock.set(false);
}
}
3. 只能保证单个变量的原子性
CAS 只能保证单个共享变量的原子操作。当操作涉及多个变量时,CAS 无效,需要其他机制来保证多变量的一致性。
解决方案:使用 AtomicReference
java
class Pair {
final int a;
final int b;
Pair(int a, int b) {
this.a = a;
this.b = b;
}
}
AtomicReference<Pair> atomicPair = new AtomicReference<>(new Pair(1, 2));
Pair expectedPair = new Pair(1, 2);
Pair newPair = new Pair(3, 4);
boolean result = atomicPair.compareAndSet(expectedPair, newPair);
4. 复杂性
使用 CAS 需要开发者理解其工作原理和适用场景,对于一些复杂的并发问题,正确使用 CAS 可能比较困难,容易引入错误。
CAS 的经典应用场景
1. 原子变量操作
CAS 最常见的应用场景是原子变量操作,例如 AtomicInteger、AtomicLong、AtomicReference 等。这些类都基于 CAS 实现了线程安全的原子操作。
示例:AtomicInteger 的实现
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public void increment() {
atomicInteger.incrementAndGet();
}
public int get() {
return atomicInteger.get();
}
public static void main(String[] args) {
AtomicIntegerExample example = new AtomicIntegerExample();
example.increment();
System.out.println("Value: " + example.get());
}
}
AtomicInteger 的 incrementAndGet 方法使用了 CAS 操作:
java
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
2. 无锁队列
CAS 可以用于实现无锁队列,例如著名的 Michael-Scott 队列(MSQueue)。这种队列在高并发场景下有显著的性能优势。
示例:简单的无锁队列实现
java
import java.util.concurrent.atomic.AtomicReference;
public class LockFreeQueue<T> {
private static class Node<T> {
final T item;
final AtomicReference<Node<T>> next;
Node(T item, Node<T> next) {
this.item = item;
this.next = new AtomicReference<>(next);
}
}
private final AtomicReference<Node<T>> head;
private final AtomicReference<Node<T>> tail;
public LockFreeQueue() {
Node<T> dummy = new Node<>(null, null);
head = new AtomicReference<>(dummy);
tail = new AtomicReference<>(dummy);
}
public void enqueue(T item) {
Node<T> newNode = new Node<>(item, null);
while (true) {
Node<T> currentTail = tail.get();
Node<T> tailNext = currentTail.next.get();
if (currentTail == tail.get()) {
if (tailNext != null) {
// Queue is in intermediate state, advance the tail
tail.compareAndSet(currentTail, tailNext);
} else {
// Try to link new node
if (currentTail.next.compareAndSet(null, newNode)) {
// Enqueue is done, try to swing tail to the inserted node
tail.compareAndSet(currentTail, newNode);
return;
}
}
}
}
}
public T dequeue() {
while (true) {
Node<T> currentHead = head.get();
Node<T> currentTail = tail.get();
Node<T> next = currentHead.next.get();
if (currentHead == head.get()) {
if (currentHead == currentTail) {
if (next == null) {
return null; // Queue is empty
}
tail.compareAndSet(currentTail, next);
} else {
T value = next.item;
if (head.compareAndSet(currentHead, next)) {
return value;
}
}
}
}
}
}
3. 无锁栈
CAS 还可以用于实现无锁栈。例如,Treiber 栈是一种常见的无锁栈实现。
示例:无锁栈的实现
java
import java.util.concurrent.atomic.AtomicReference;
public class LockFreeStack<T> {
private static class Node<T> {
final T value;
final Node<T> next;
Node(T value, Node<T> next) {
this.value = value;
this.next = next;
}
}
private final AtomicReference<Node<T>> top = new AtomicReference<>(null);
public void push(T value) {
Node<T> newNode = new Node<>(value, null);
while (true) {
Node<T> currentTop = top.get();
newNode.next = currentTop;
if (top.compareAndSet(currentTop, newNode)) {
return;
}
}
}
public T pop() {
while (true) {
Node<T> currentTop = top.get();
if (currentTop == null) {
return null; // Stack is empty
}
Node<T> newTop = currentTop.next;
if (top.compareAndSet(currentTop, newTop)) {
return currentTop.value;
}
}
}
}
4. 无锁计数器
CAS 在实现高效的无锁计数器时非常有用。
示例:无锁计数器
java
import java.util.concurrent.atomic.AtomicInteger;
public class LockFreeCounter {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
while (true) {
int current = counter.get();
int next = current + 1;
if (counter.compareAndSet(current, next)) {
return;
}
}
}
public int get() {
return counter.get();
}
public static void main(String[] args) {
LockFreeCounter counter = new LockFreeCounter();
counter.increment();
System.out.println("Counter: " + counter.get());
}
}
5. ConcurrentHashMap
ConcurrentHashMap 是 Java 并发包中的一个高效线程安全的哈希表实现,它使用了分段锁机制和 CAS 操作来实现高效的并发访问。
示例:ConcurrentHashMap 的部分实现
ConcurrentHashMap 通过 CAS 实现了高效的插入和更新操作:
java
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 省略其他处理逻辑...
}
addCount(1L, binCount);
return null;
}
总结
CAS 作为一种高效的无锁机制,在并发编程中具有显著的优点,如高性能、避免死锁等。然而,它也存在诸如 ABA 问题、自旋开销大等缺点。在实际开发中,合理选择和使用 CAS,可以有效提高程序的并发性能和稳定性。