Java中原子操作的比较和交换 (CAS)

在本文中,我们将深入研究 Java CAS 的机制,揭开它的神秘面纱并了解它如何在实现非阻塞方法方面发挥关键作用。

什么是比较和交换 (CAS) 
想象一下,你和朋友各有一篮子苹果,你们都想交换一些苹果。

现在,CAS 就像一个公平交易系统一样介入了。你们都向对方展示自己的苹果,只有当你们的苹果数量相同时,交换才会发生。如果有人在交易过程中摘取或添加了苹果,交易就会被取消。

在 Java 中,CAS 也有类似的功能,它能确保只有在所有数据都同步的情况下,才会对共享数据进行更改。这就好比确保数字苹果交换顺利进行,不会出现任何意外。

在 Java 中,比较和交换(CAS)操作是管理共享变量并发访问的强大工具。它确保对共享数据的修改以原子方式进行,从而防止在多线程环境中发生冲突。让我们探索一下 Java 如何通过 java.util.concurrent.atomic 包实现 CAS。

Atomic类
Java 提供了一组原子类,可封装变量并提供原子操作。这些类在内部使用 CAS 来实现线程安全更新。最常见的有 AtomicInteger、AtomicLong 和 AtomicReference。下面是一个使用 AtomicInteger 的简单示例:

import java.util.concurrent.atomic.AtomicInteger;
 
public class CASExample {
    private static AtomicInteger counter = new AtomicInteger(0);
 
    public static void main(String[] args) {
        // Increment operation using CAS
        int oldValue = counter.get();
        int newValue = oldValue + 1;
        while (!counter.compareAndSet(oldValue, newValue)) {
           
// Retry if the compare-and-swap fails
            oldValue = counter.get();
            newValue = oldValue + 1;
        }
 
        System.out.println(
"Updated Counter: " + counter.get());
    }
}

CAS 操作剖析

  • 读取当前值:get() 方法读取 AtomicInteger 的当前值。
  • 计算新值:根据当前值计算新值。在本例中,我们的增量为 1。
  • CAS 循环:compareAndSet(oldValue,newValue)方法执行 CAS 操作。它会检查当前值是否与我们最初读取时的值相同。如果是,则将值更新为新值;如果不是,则重试操作。
  • 重试机制:如果 CAS 失败(表明另一个线程修改了值),我们将进入重试循环,重新读取当前值并再次尝试 CAS。

CAS 使用案例

  • 计数器递增:如示例所示,CAS 对于多个线程同时递增计数器而不会导致竞赛条件的情况非常方便。
  • 引用更新:在需要以原子方式更新对象引用的情况下,AtomicReference 也可以类似地使用。
  • 条件更新:CAS 在实现条件更新方面功能强大,允许您仅在变量满足特定条件时对其进行修改。

CAS 的好处

  • 非阻塞: CAS 提供了一种非阻塞方法,允许线程即使在存在争用的情况下也能取得进展。
  • 避免锁:与传统锁不同,CAS 避免了显式锁定机制的需要,从而减少了争用并提高了性能。
  • 可预测的行为: CAS 确保原子性,使其成为并发编程中管理共享状态的可靠选择。

Java CAS实现的本质
在并发编程中,Java的比较和交换(CAS)依赖于低级硬件支持的基础,特别是依赖于现代处理器中嵌入的比较和交换(CAS)指令。实现这种复杂操作的关键是类Unsafe,尽管它的可访问性有限,但它充当直接内存操作的管道——这是在不受传统锁束缚的情况下实现原子操作的重要组成部分。

让我们来剖析 AtomicInteger 类中 compareAndSet 方法的简化版,以了解 Java CAS 的内部工作原理:

public final class AtomicInteger extends Number implements java.io.Serializable {
    private volatile int value;
    private static final long valueOffset;
 
    static {
        try {
            valueOffset = Unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
 
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
//关键
    }
   
// Other methods omitted for brevity
}

CAS 背后的关键机制是类compareAndSwapInt中的方法Unsafe,它对四个关键参数进行操作:

  • Object obj:包含要更新的字段的对象。
  • long offset:字段在对象内的偏移量。
  • int expected:该字段的预期值。
  • int x:要设置的新值。

每个步骤详细情况:

  1. 偏移量计算:
    • 在类初始化期间,valueOffset使用 进行计算Unsafe.objectFieldOffset。value该偏移量表示该字段在对象内的内存位置AtomicInteger。
  • 内存访问:
    • compareAndSwapIntvalue使用计算出的偏移量来访问与对象内的字段相对应的内存位置AtomicInteger。
  • 原子比较和交换:
    • 实际的 CAS 操作以原子方式展开。它检查指定内存位置的当前值是否与预期值 ( expect) 一致。如果比较成功,则新值 ( x) 会自动写入内存位置。
  • 结果指示:
    • 该方法提供一个布尔判断,表示 CAS 操作的成功或失败。胜利的匹配会产生true,而失配则会产生false。

    这种与内存和硬件指令的密切交互使 CAS 成为一种强大的工具,可以实现线程安全,而无需锁的阻碍。


    在不同场景中应用 CAS
    为了说明 CAS 的实用性,让我们考虑两个新颖的场景:协调无阻塞票务系统和制作无锁队列。

    1. 非阻塞票务系统

    import java.util.concurrent.atomic.AtomicInteger;
     
    public class CASTicketingSystem {
        private static AtomicInteger availableTickets = new AtomicInteger(100);
     
        public boolean bookTicket(int requestedTickets) {
            while (true) {
                int currentAvailable = availableTickets.get();
                if (currentAvailable < requestedTickets || 
                    !availableTickets.compareAndSet(currentAvailable, currentAvailable - requestedTickets)) {
                    return false; // Unable to book tickets
                }
                return true;
    // Tickets booked successfully
            }
        }
    }

    在这种情况下,CAS 用于创建非阻塞票务系统。bookTicket方法确保可以同时处理多个票证请求而不会发生冲突。

    2. 无锁队列实现

    import java.util.concurrent.atomic.AtomicReference;
     
    class QueueNode<T> {
        T data;
        QueueNode<T> next;
     
        QueueNode(T data) {
            this.data = data;
            this.next = null;
        }
    }
     
    public class CASLockFreeQueue<T> {
        private AtomicReference<QueueNode<T>> head = new AtomicReference<>();
        private AtomicReference<QueueNode<T>> tail = new AtomicReference<>();
     
        public void enqueue(T data) {
            QueueNode<T> newNode = new QueueNode<>(data);
            while (true) {
                QueueNode<T> currentTail = tail.get();
                if (currentTail == null) {
                    head.set(newNode); // Queue is empty, set both head and tail to the new node
                    tail.set(newNode);
                    return;
                }
                currentTail.next = newNode;
                if (tail.compareAndSet(currentTail, newNode)) {
                    return;
    // Enqueue successful
                }
            }
        }
     
        public T dequeue() {
            while (true) {
                QueueNode<T> currentHead = head.get();
                if (currentHead == null) {
                    return null;
    // Queue is empty
                }
                QueueNode<T> newHead = currentHead.next;
                if (head.compareAndSet(currentHead, newHead)) {
                    return currentHead.data;
    // Dequeue successful
                }
            }
        }
    }

    在本例中,利用CAS构建无锁队列,确保入队和出队操作可以并发执行,而不需要传统的锁。