什么是CAS?

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,可以有效提高程序的并发性能和稳定性。

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值