CAS (Compare and Swap) 笔记251007

CAS (Compare and Swap) 笔记251007

好的,我们来详细、深入地解析一下 CAS(Compare and Swap)

CAS 是现代并发编程的基石之一,它使得无锁(Lock-Free)和高性能的并发算法成为可能。


1. 什么是 CAS?

CAS 是一种原子操作 。它用于在多线程环境下实现同步,而无需使用重量级的锁(如 synchronizedReentrantLock)。

它的核心思想是:"我认为内存位置 V 的值应该是 A,如果是,那么把它更新为 B;否则,不要修改它,但要告诉我当前的实际值是什么。"

一个 CAS 操作包含三个操作数:

  1. 内存位置(V)
  2. 预期原值(A)
  3. 新值(B)

CAS 的语义可以用以下伪代码来表示:

typescript 复制代码
boolean CAS(V, A, B) {
    if (V == A) { // 只有在内存值等于预期原值时,才进行更新
        V = B
        return true // 表示更新成功
    } else {
        return false // 表示更新失败
    }
}

关键在于,整个比较和交换的过程是一个单一的、不可中断的原子操作 。CPU 提供了一条特殊的指令(在 x86 架构上是 CMPXCHG)来保证这一点。


2. CAS 的工作原理

当多个线程同时尝试对同一个内存位置进行 CAS 操作时,会发生什么?

  1. 线程 1 和线程 2 都从主内存中读取变量 V,假设值为 100
  2. 它们都打算将 100 增加 1,所以它们都计算出新值 101
  3. 线程 1 率先执行 CAS 操作:
    • 它检查 V 的值是否还是它之前读到的 100
    • 此时确实是 100,所以 CAS 成功。V 被更新为 101CAS(V, 100, 101) 返回 true
  4. 线程 2 紧接着执行 CAS 操作:
    • 它检查 V 的值是否还是它之前读到的 100
    • 但此时 V 已经被线程 1 改为 101 了。
    • 值不匹配,所以 CAS 失败。 CAS(V, 100, 101) 返回 false
  5. 线程 2 的 CAS 操作失败后,它并不会阻塞或挂起 。典型的做法是重试(自旋)
    • 它重新读取 V 的当前值(现在是 101)。
    • 重新计算新值(101 + 1 = 102)。
    • 再次发起 CAS 操作:CAS(V, 101, 102)
    • 这次因为值匹配,所以很可能成功。

这个过程就像在说:"我先看看现在是多少,算好新值,然后尝试更新。如果有人在我之前改动了,那我就再试一次。"


3. CAS 在 Java 中的实现

在 Java 中,CAS 操作是通过 sun.misc.Unsafe 类中的本地(Native)方法实现的。但我们通常不会直接使用 Unsafe 类,而是使用 JDK 封装好的原子类。

最经典的例子就是 java.util.concurrent.atomic.AtomicInteger

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(100);

        // 模拟线程1
        boolean success1 = atomicInt.compareAndSet(100, 101); // CAS 操作
        System.out.println("Thread 1 CAS: " + success1 + ", value: " + atomicInt.get()); // true, 101

        // 模拟线程2
        boolean success2 = atomicInt.compareAndSet(100, 101); // CAS 操作,但预期值已是旧的100
        System.out.println("Thread 2 CAS: " + success2 + ", value: " + atomicInt.get()); // false, 101

        // 线程2重试
        boolean success3 = atomicInt.compareAndSet(101, 102); // 使用新读到的值101作为预期值
        System.out.println("Thread 2 Retry CAS: " + success3 + ", value: " + atomicInt.get()); // true, 102
    }
}

输出:

yaml 复制代码
Thread 1 CAS: true, value: 101
Thread 2 CAS: false, value: 101
Thread 2 Retry CAS: true, value: 102

compareAndSet 方法就是 Java 中 CAS 操作最直接的体现。


4. CAS 的典型应用场景

  1. 无锁计数器 :如上文的 AtomicInteger,实现线程安全的递增递减。
  2. 乐观锁:在数据库和并发控制中非常常见。例如,更新数据前先读取一个版本号,更新时检查版本号是否变化,没变化则更新,变化了则重试或报错。
  3. 实现复杂的无锁数据结构 :如 ConcurrentLinkedQueue(无锁队列)、ConcurrentHashMap(在 JDK 1.8 的部分实现中使用了 CAS)等高性能并发容器。

5. CAS 的优缺点

优点:
  1. 高性能

    • 避免了线程阻塞和上下文切换的开销。在低到中度锁竞争的情况下,性能远高于传统的锁。
    • 线程在 CAS 失败后立即重试,响应速度快。
  2. 避免死锁:因为不使用锁,所以从根本上避免了死锁问题。

缺点:
  1. ABA 问题

    • 这是 CAS 最著名的陷阱。
    • 描述:一个变量原来是 A,被线程 1 读取。之后,线程 2 将其修改为 B,然后又修改回 A。此时,线程 1 执行 CAS 操作,发现值还是 A,于是误以为它没有被修改过,CAS 成功。
    • 在某些场景下这是有问题的,例如一个无锁栈,如果头节点经历了 A->B->A 的变化,虽然值没变,但整个栈的结构可能已经变了。
    • 解决方案 :使用版本号/标记 。每次变量更新时,版本号都递增。Java 提供了 AtomicStampedReferenceAtomicMarkableReference 来解决 ABA 问题。
  2. 循环时间长开销大

    • 如果 CAS 操作长时间不成功(竞争非常激烈),CPU 会一直进行自旋,消耗大量 CPU 资源。
    • 在这种情况下,传统的锁(让竞争失败的线程挂起)可能反而是更好的选择。
  3. 只能保证一个共享变量的原子操作

    • 单个 CAS 操作只能针对一个变量。如果你需要对多个变量进行原子操作,就比较麻烦。
    • 解决方案 :可以将多个变量合并到一个对象中,然后使用 AtomicReference 来保证这个对象引用的原子性。

6、CAS 的三大经典问题

尽管 CAS 非常高效,但它也存在一些著名的缺陷:

1. ABA 问题
  • 描述:一个线程读取内存值为 A,随后另一个线程将其修改为 B,然后又修改回 A。此时,第一个线程执行 CAS 时,会发现值仍然是 A,于是误认为它没有被修改过,CAS 操作成功。这在某些依赖于值的历史状态的场景下会出问题(例如链表操作)。
  • 解决方案 :使用版本号标记 。Java 中提供了 AtomicStampedReference(维护一个版本号戳)和 AtomicMarkableReference(维护一个布尔标记)来解决这个问题。
2. 循环时间长开销大
  • 描述 :如果 CAS 操作长时间不成功(例如,竞争非常激烈),for 循环会不停地重试,会占用大量的 CPU 资源。
  • 解决方案:对于高竞争的场景,CAS 可能不是最佳选择。可以使用 LongAdder(JDK8),它采用分段锁的思想来降低竞争;或者退回到使用传统的互斥锁。
3. 只能保证一个共享变量的原子操作
  • 描述:一个 CAS 操作只能针对一个共享变量。
  • 解决方案 :如果需要同时原子性地更新多个变量,可以将多个变量封装到一个对象中。Java 提供了 AtomicReference 来保证引用类型对象的原子性,从而间接实现多个变量的原子更新。

总结

特性 描述
全称 Compare And Swap(比较并交换)
本质 一条 CPU 原子指令
核心思想 我认为 V 的值应该是 A,如果是,则更新为 B;否则,不更新并告知失败。
在 Java 中的体现 AtomicXXX 类(如 AtomicInteger)、Unsafe
优势 高性能(无阻塞)、避免死锁
劣势 ABA 问题、高竞争下 CPU 空耗、只能保证一个变量的原子性

核心要点:

CAS 是乐观锁 的一种实现方式。它假设冲突很少发生,所以先大胆地去操作,如果发现冲突(CAS 失败),再重试。这与传统的悲观锁 (如 synchronized)形成鲜明对比,悲观锁假设冲突经常发生,所以每次操作前都要先加锁,确保独占访问。

理解 CAS 是理解现代高性能并发库(如 java.util.concurrent 包)如何工作的关键。





1️⃣ 参考 1

CAS 是现代计算机科学中一个极其重要的概念,它是构建无锁并发数据结构、实现高效同步机制的基石。


1. 什么是 CAS?

CAS ,全称 Compare and Swap ,是一种原子操作 。它用于在多线程环境下实现同步,而无需使用传统的锁(如 synchronizedReentrantLock)。

它的核心思想是:"我认为值 V 应该是 A,如果是,那么把它更新为 B;否则,不要修改它,但要告诉我现在实际的值是什么。"

2. CAS 的操作语义

CAS 操作通常包含三个操作数:

  1. 内存位置(V)
  2. 预期原值(A)
  3. 新值(B)

其语义可以用以下伪代码来理解:

typescript 复制代码
boolean CAS(V, A, B) {
    if (V == A) { // 只有在当前值符合预期时,才进行更新
        V = B;
        return true; // 成功
    } else {
        return false; // 失败
    }
}

关键在于,这个比较和交换的动作是一个不可分割的原子操作。 在现代CPU上,这通常是由一条特殊的硬件指令(如 x86 架构上的 CMPXCHG)来完成的,从而保证了在多个线程同时尝试 CAS 同一个内存位置时,不会出现数据竞争。

3. 一个生动的例子:乐观锁

想象一个多人协同编辑的文档(比如 Google Docs)。

  • 悲观锁方式 :你告诉所有人"我要开始编辑了,在我保存之前你们都不能动"。(这对应于 synchronized,你获取了锁,其他线程被阻塞)。
  • 乐观锁方式(CAS方式)
    1. 你打开文档,看到内容是 "Hello"(这是你的 预期原值 A)。
    2. 你开始本地编辑,将 "Hello" 改为 "Hello World!"(这是你的 新值 B)。
    3. 你点击保存。系统在执行保存操作时,会执行一个 CAS
      • 检查 :当前服务器上的文档内容是否还是 "Hello"?(比较 V == A
      • 如果 ,说明在你编辑期间没有其他人修改,系统将内容更新为 "Hello World!",保存成功
      • 如果 ,说明在你编辑期间(比如小王把内容改成了 "Hi"),系统不会覆盖小王的修改,你的保存失败 。你需要做的是:重新拉取最新的文档(获取新的预期值) ,然后基于新内容再次进行编辑和保存(重试)。

这个"保存时检查"的过程,就是 CAS 的精髓。它避免了长时间的阻塞,允许多个参与者同时工作,只在最后提交时解决冲突。

4. CAS 在 Java 中的实现

在 Java 中,CAS 操作并不是通过关键字直接暴露给开发者的,而是通过 sun.misc.Unsafe 类中的本地方法(compareAndSwapObject, compareAndSwapInt, compareAndSwapLong)来实现的。

然而,对于普通开发者,更常用的是 java.util.concurrent.atomic 包下的原子类,它们封装了这些底层的 CAS 操作。

AtomicInteger 为例:

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(0); // 初始值为 0

        // 模拟一个典型的 CAS 操作:如果当前值是 0,就把它设置为 1
        boolean success1 = atomicInt.compareAndSet(0, 1); // CAS(V=atomicInt, A=0, B=1)
        System.out.println("第一次 CAS: " + success1 + ", 当前值: " + atomicInt.get()); // 输出: true, 1

        // 再次尝试:当前值已经是 1 了,但我们仍预期它是 0
        boolean success2 = atomicInt.compareAndSet(0, 2); // CAS(V=atomicInt, A=0, B=2)
        System.out.println("第二次 CAS: " + success2 + ", 当前值: " + atomicInt.get()); // 输出: false, 1
    }
}

另一个核心方法:getAndIncrement()

我们经常使用的 i++ 操作不是原子的。但 AtomicIntegergetAndIncrement() 是原子的,它的内部就是通过 CAS 实现的,通常被称为 自旋

java 复制代码
public final int getAndIncrement() {
    // 这是一个典型的自旋循环 CAS 模式
    for (;;) { // 循环,直到成功为止
        int current = get(); // 获取当前值,作为预期原值 A
        int next = current + 1; // 计算新值 B
        if (compareAndSet(current, next)) { // 尝试 CAS
            return current; // 如果成功,返回旧值
        }
        // 如果失败,循环重试
    }
}

5. CAS 的优缺点

优点:

  1. 高性能:在竞争不激烈的情况下,它避免了线程挂起和上下文切换的开销,比传统的锁拥有更好的性能。
  2. 避免死锁:由于是无锁操作,从根本上避免了死锁问题。
  3. 非阻塞性:一个线程的失败不会导致其他线程阻塞,它们可以立即重试或进行其他操作。

缺点:

  1. ABA 问题

    • 描述:假设一个变量的值原来是 A,被线程 1 读到了。之后,线程 2 将其修改为 B,然后又修改回 A。这时,线程 1 执行 CAS 操作,发现值还是 A,于是认为没有人修改过,CAS 成功。但这可能是一个错误的前提,因为变量的历史发生了变化。
    • 例子:你看到冰箱里有一瓶可乐(A),然后你去干活了。你老婆把可乐喝了,把瓶子扔了(B),然后觉得过意不去,又买了一瓶新的同款可乐放回冰箱(A)。你回来一看,可乐还在,以为没人动过。
    • 解决方案 :使用版本号标记 。Java 中提供了 AtomicStampedReferenceAtomicMarkableReference 来解决 ABA 问题。它们不仅在比较值,还会比较一个版本号(Stamp)。
  2. 循环时间长开销大

    • 如果 CAS 操作长时间不成功(竞争激烈),for 循环会一直重试,会占用大量的 CPU 资源。如果线程数非常多,不如使用传统的锁效率高。
  3. 只能保证一个共享变量的原子操作

    • 一个 CAS 操作只能作用于一个共享变量。如果要保证多个变量的原子性,可以尝试将它们合并到一个对象中,然后使用 AtomicReference 来 CAS 这个对象。

6. 总结

特性 CAS(乐观锁) 传统锁(悲观锁)
核心思想 先操作,冲突时重试 先获取锁,再操作
阻塞性 非阻塞 阻塞
性能 低竞争下,性能极高 高竞争下,可能更稳定
缺点 ABA问题、CPU空转 死锁、性能开销大
适用场景 计数器、栈顶/队头更新等简单操作 复杂的临界区操作

总而言之,CAS 是一种强大的低级原语,它为实现高效、无锁的并发算法提供了可能。 虽然直接使用它的场景有限,但理解其原理对于使用 java.util.concurrent 包、理解数据库的 MVCC 机制以及设计高性能系统都至关重要。





2️⃣ 参考 2

CAS(Compare and Swap)。这是一个在并发编程中至关重要的核心概念。


1. 什么是 CAS?

CAS ,全称 Compare and Swap(比较并交换),是一种用于实现多线程同步的原子操作

它的核心思想是:"我认为V的值应该是A,如果是,那么将它更新为B;否则,不要修改它,但要告诉我现在实际的值是多少。"

一个 CAS 操作包含三个操作数:

  1. 内存位置(V)
  2. 预期原值(A)
  3. 新值(B)

2. CAS 的工作原理

CAS 的运作流程可以用以下伪代码来理解:

arduino 复制代码
boolean compareAndSwap(MemoryAddress V, int A, int B) {
    if (*V == A) { // 步骤1:比较
        *V = B;    // 步骤2:交换
        return true;
    } else {
        return false;
    }
}

具体步骤:

  1. 比较 :检查内存位置 V 的当前值是否与预期原值 A 相等。
  2. 交换
    • 如果相等,处理器会自动将该位置的值更新为新值 B,并返回 true 表示操作成功。
    • 如果不相等,说明在此期间有其他线程修改了 V 的值。操作失败,不会进行任何更新,并返回 false。此时,调用者可以获取当前的最新值,并重新尝试操作。

关键在于,整个"比较-交换"过程是一个单一的、不可分割的硬件原子指令。这意味着在操作过程中,不会被其他线程中断,从而保证了多线程环境下的数据安全。

3. 一个生动的例子

假设一个共享变量 count 的初始值为 5。

线程A线程B 都想将其增加 1。

  • 线程A 执行 CAS 操作:

    • V = &count, A = 5, B = 6
    • CAS(&count, 5, 6) -> 成功!count 现在为 6。
  • 几乎同时,线程B 也试图执行 CAS 操作:

    • V = &count, A = 5, B = 6 (注意:线程B读取到的预期原值还是旧的 5)
    • CAS(&count, 5, 6) -> 失败!因为 count 的当前值 (6) 不等于它的预期原值 (5)。
  • 线程B 的操作失败后,它会进行"自旋"

    • 它重新读取 count 的最新值 (6)。
    • 然后发起新的 CAS 操作:CAS(&count, 6, 7)
    • 这次操作成功,count 被更新为 7。

这个过程就像是一个乐观的并发控制策略:我先假定没什么人和我竞争,如果事实证明我错了,那我就再试一次。

4. CAS 在 Java 中的实现

在 Java 中,CAS 操作是通过 sun.misc.Unsafe 类中的本地方法(Native Methods)实现的。这些方法最终会调用处理器底层的原子指令。

对于我们普通开发者来说,最直接的接触点是 java.util.concurrent.atomic 包下的原子类,例如:

  • AtomicInteger
  • AtomicLong
  • AtomicReference
  • AtomicStampedReference (用于解决 ABA 问题)

示例:使用 AtomicInteger

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                count.incrementAndGet(); // 底层使用 CAS 实现
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                count.incrementAndGet(); // 底层使用 CAS 实现
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final count: " + count.get()); // 总是 20000
    }
}

incrementAndGet() 的内部实现就是一个典型的 CAS 自旋:

java 复制代码
public final int incrementAndGet() {
    for (;;) { // 自旋循环
        int current = get(); // 获取当前值
        int next = current + 1; // 计算新值
        if (compareAndSet(current, next)) { // 尝试 CAS
            return next; // 成功则返回
        }
        // 失败则立即重试
    }
}

5. CAS 的优缺点

优点:

  1. 高性能 :相比传统的重量级锁(如 synchronized),CAS 避免了线程的阻塞和唤醒,这是一种在用户态完成的轻量级操作,减少了内核态和用户态切换的开销。在竞争不激烈的情况下,性能远高于锁。
  2. 避免死锁:由于线程不会阻塞,从根本上避免了死锁问题。

缺点:

  1. ABA 问题

    • 描述:一个变量原来是 A,被线程1改为 B,然后又被人改回了 A。这时,线程2进行 CAS 操作时,会发现当前值确实是 A,于是误以为它没有被修改过,从而操作成功。这对于只关心结果值的操作可能没问题,但如果过程很重要(例如链表的头节点),就会出问题。
    • 解决方案 :使用版本号或时间戳。Java 提供了 AtomicStampedReference,它不仅在更新值时比较引用,还会比较一个 int 类型的版本戳(Stamp)。
  2. 循环时间长开销大

    • 如果 CAS 操作长时间不成功(竞争激烈),会导致线程长时间自旋,白白消耗 CPU 资源。如果线程数非常多,对 CPU 的消耗会很大。
  3. 只能保证一个共享变量的原子操作

    • 当对一个共享变量执行操作时,可以使用循环 CAS 的方式来保证原子操作。但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性。
    • 解决方案
      • 可以用锁。
      • 可以将多个变量合并到一个对象里,然后使用 AtomicReference 来保证这个复合对象的原子性。

总结

特性 描述
核心思想 无锁编程、乐观锁。先尝试更新,如果失败就重试。
操作原子性 由硬件(CPU)指令保证,是现代多核处理器并发的基础。
在 Java 中 通过 Unsafe 类和 java.util.concurrent.atomic 包下的原子类暴露给开发者。
主要优势 高性能,在低到中度竞争环境下表现优异。
主要挑战 ABA 问题、自旋导致的 CPU 开销、只能保证单个变量的原子性。

CAS 是现代并发包(如 Java 的 java.util.concurrent)的基石,理解了 CAS,就理解了为什么在高并发场景下,无锁算法和数据结构能够如此高效。





3️⃣ 参考 3

CAS (Compare and Swap) 详解

什么是 CAS

CAS (Compare and Swap,比较并交换)是一种无锁的原子操作,用于实现多线程环境下的同步机制。它通过硬件指令保证操作的原子性,是现代并发编程中的重要基础。

CAS 工作原理

基本操作流程

diff 复制代码
CAS(V, E, N)
- V: 要更新的内存位置
- E: 期望的旧值
- N: 要设置的新值

操作步骤:

  1. 比较内存位置 V 的值是否与期望值 E 相等
  2. 如果相等,则将内存位置 V 的值更新为新值 N
  3. 如果不相等,说明有其他线程修改了该值,操作失败

伪代码实现

cpp 复制代码
bool CAS(int* ptr, int expected, int new_value) {
    if (*ptr == expected) {
        *ptr = new_value;
        return true;
    }
    return false;
}

CAS 的特点

优点

  1. 无锁编程:避免线程阻塞,提高性能
  2. 原子性:硬件保证操作的原子性
  3. 高并发:多个线程可以同时竞争,不会阻塞

缺点

  1. ABA 问题:值从 A 变为 B 又变回 A,CAS 会误认为没有变化
  2. 循环时间长开销大:如果竞争激烈,会导致长时间自旋
  3. 只能保证一个变量的原子操作:对于多个变量的原子操作需要额外处理

Java 中的 CAS 实现

Atomic 类使用示例

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        int oldValue, newValue;
        do {
            oldValue = count.get();          // 获取当前值
            newValue = oldValue + 1;         // 计算新值
        } while (!count.compareAndSet(oldValue, newValue)); // CAS 操作
    }
    
    public int getCount() {
        return count.get();
    }
}

简化写法

java 复制代码
public class SimpleCASExample {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // 内部使用 CAS 实现
    }
}

解决 ABA 问题

使用版本号/标记

java 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAExample {
    private AtomicStampedReference<Integer> atomicRef = 
        new AtomicStampedReference<>(0, 0);
    
    public void updateValue(int expectedValue, int newValue) {
        int[] stampHolder = new int[1];
        int currentValue = atomicRef.get(stampHolder);
        int currentStamp = stampHolder[0];
        
        // 比较值和版本号
        if (currentValue == expectedValue) {
            atomicRef.compareAndSet(expectedValue, newValue, 
                                  currentStamp, currentStamp + 1);
        }
    }
}

CAS 底层实现

硬件支持

  • x86 : CMPXCHG 指令
  • ARM : LDREX/STREX 指令对
  • 这些指令在硬件层面保证了比较和交换的原子性

Java 中的 Unsafe 类

java 复制代码
// Java 底层通过 Unsafe 类实现 CAS
public final class AtomicInteger extends Number {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    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);
    }
}

实际应用场景

1. 计数器

java 复制代码
public class Counter {
    private AtomicLong count = new AtomicLong(0);
    
    public long increment() {
        return count.incrementAndGet();
    }
}

2. 非阻塞栈

java 复制代码
public class ConcurrentStack<E> {
    private AtomicReference<Node<E>> top = new AtomicReference<>();
    
    public void push(E item) {
        Node<E> newHead = new Node<>(item);
        Node<E> oldHead;
        do {
            oldHead = top.get();
            newHead.next = oldHead;
        } while (!top.compareAndSet(oldHead, newHead));
    }
    
    public E pop() {
        Node<E> oldHead;
        Node<E> newHead;
        do {
            oldHead = top.get();
            if (oldHead == null) return null;
            newHead = oldHead.next;
        } while (!top.compareAndSet(oldHead, newHead));
        return oldHead.item;
    }
    
    private static class Node<E> {
        final E item;
        Node<E> next;
        
        Node(E item) {
            this.item = item;
        }
    }
}

3. 乐观锁

java 复制代码
public class OptimisticLockExample {
    private AtomicInteger version = new AtomicInteger(0);
    private String data;
    
    public boolean updateData(String newData, int expectedVersion) {
        // 检查版本号是否变化
        if (version.compareAndSet(expectedVersion, expectedVersion + 1)) {
            this.data = newData;
            return true;
        }
        return false; // 版本冲突,更新失败
    }
}

性能考虑

适用场景

  • 低竞争环境:CAS 性能优于锁
  • 短时操作:CAS 操作应该快速完成
  • 读多写少:读操作不需要同步

不适用场景

  • 高竞争环境:大量线程竞争同一资源会导致频繁自旋
  • 长时间操作:CAS 循环会占用 CPU 资源
  • 复杂操作:需要保护多个变量的操作

总结

CAS 是现代并发编程的核心技术之一,它提供了高效的无锁同步机制。虽然存在 ABA 问题和高竞争下的性能问题,但在合适的场景下,CAS 能够显著提升程序性能。理解 CAS 的原理和适用场景对于编写高性能的并发程序至关重要。





4️⃣ 参考 4

我们来对 CAS(Compare and Swap) 进行一次详细的解析。这是一个在并发编程中至关重要的核心概念。


一、什么是 CAS?

CAS ,全称 Compare and Swap ,中文可译为"比较并交换"。它是一种原子操作,用于在多线程环境下实现无锁(lock-free)的线程安全。

它的核心思想是:"我认为V的值应该是A,如果是,那么将V的值更新为B;否则,不修改并告诉我当前V的实际值是多少。"

二、CAS 的工作原理

一个 CAS 操作包含三个操作数:

  1. 内存位置(V)
  2. 预期原值(A)
  3. 新值(B)

其执行流程可以用以下伪代码来理解:

kotlin 复制代码
function CAS(V, A, B) is
    // 1. 读取内存位置V的当前值
    temp = *V
    // 2. 比较当前值是否与预期原值A相等
    if temp == A then
        // 3. 如果相等,说明内存值未被其他线程修改,则将新值B写入V
        *V = B
        // 4. 返回成功
        return true
    else
        // 5. 如果不相等,说明内存值已经被其他线程修改,则操作失败。返回当前的实际值(或失败状态)
        return false (and optionally return temp)

这个过程是原子性的,意味着在比较和交换之间,不会有其他线程插入来修改内存位置V的值。

一个生动的比喻:

假设一个共享储物柜(内存位置 V),你认为里面放的是你的书(预期原值 A),你想用你的笔记本电脑(新值 B)换回这本书。

  1. 你打开储物柜(读取当前值)。
  2. 你看到里面放的确实是你想找的书(当前值 == A)。
  3. 你立刻把书拿出来,并把笔记本电脑放进去(执行交换,V = B)。操作成功!

但是,如果在你看储物柜(步骤1)和准备交换(步骤3)的瞬间,你的朋友过来把他的书拿走了,并放了一个水杯进去。那么:

  1. 你打开储物柜(读取当前值)。
  2. 你发现里面是一个水杯(当前值 != A)。
  3. 你意识到情况有变,交换失败。你现在知道了储物柜里的最新东西是水杯(新的预期值)。

三、CAS 的底层硬件实现

CAS 之所以能成为原子操作,并不是通过软件锁实现的,而是依赖于底层硬件(主要是CPU)提供的指令。

  • x86 架构CMPXCHG 指令(Compare and Exchange)。
  • ARM 架构LDREXSTREX 指令对,用于实现类似的加载-链接/条件存储操作。

现代的多核处理器都在硬件层面直接支持这些原子操作指令,确保了它们在多核环境下的原子性。

四、CAS 在 Java 中的应用

在 Java 中,CAS 操作是由 sun.misc.Unsafe 类中的本地方法(Native Method)提供的,例如 compareAndSwapInt, compareAndSwapLong 等。

然而,对于普通开发者来说,我们通常不会直接使用 Unsafe 类。而是通过 java.util.concurrent.atomic 包下的原子类来间接使用 CAS。

最典型的例子:AtomicInteger

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                // 这行代码底层就使用了 CAS
                count.incrementAndGet();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                count.incrementAndGet();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final count: " + count.get()); // 总是 20000
    }
}

incrementAndGet() 的内部实现,就是一个典型的 CAS + 自旋 循环:

java 复制代码
public final int incrementAndGet() {
    // 自旋循环
    for (;;) {
        int current = get(); // 获取当前值,作为预期原值 A
        int next = current + 1; // 计算新值 B
        // 调用 CAS:如果 count 的当前值还是 current,就把它更新为 next
        if (compareAndSet(current, next)) {
            return next; // 成功则返回新值
        }
        // 失败则循环重试,重新读取最新的 current 值
    }
}

五、CAS 的三大经典问题

尽管 CAS 非常高效,但它也存在一些著名的缺陷:

  1. ABA 问题
  • 描述:一个线程读取内存值为 A,随后另一个线程将其修改为 B,然后又修改回 A。此时,第一个线程执行 CAS 时,会发现值仍然是 A,于是误认为它没有被修改过,CAS 操作成功。这在某些依赖于值的历史状态的场景下会出问题(例如链表操作)。
  • 解决方案 :使用版本号标记 。Java 中提供了 AtomicStampedReference(维护一个版本号戳)和 AtomicMarkableReference(维护一个布尔标记)来解决这个问题。
  1. 循环时间长开销大
  • 描述 :如果 CAS 操作长时间不成功(例如,竞争非常激烈),for 循环会不停地重试,会占用大量的 CPU 资源。
  • 解决方案:对于高竞争的场景,CAS 可能不是最佳选择。可以使用 LongAdder(JDK8),它采用分段锁的思想来降低竞争;或者退回到使用传统的互斥锁。
  1. 只能保证一个共享变量的原子操作
  • 描述:一个 CAS 操作只能针对一个共享变量。
  • 解决方案 :如果需要同时原子性地更新多个变量,可以将多个变量封装到一个对象中。Java 提供了 AtomicReference 来保证引用类型对象的原子性,从而间接实现多个变量的原子更新。

六、CAS 的优缺点总结

优点:

  1. 高性能 :在多数低至中度竞争的情况下,它避免了线程挂起和上下文切换的开销,性能远高于使用锁(如 synchronized)。
  2. 无锁(Lock-Free):它是构建非阻塞算法的基础。

缺点:

  1. ABA 问题:如上所述,需要额外机制来解决。
  2. 自旋开销:在高竞争环境下,CPU 空转严重。
  3. 功能局限:只能保证一个变量的原子性。

七、总结

CAS 是现代并发编程的基石之一。它通过利用硬件的原子指令,提供了一种高效的无锁同步机制。理解 CAS 是理解 java.util.concurrent 包中众多高效工具类(如 ConcurrentHashMap, AQS 等)的关键。

它并非万能的银弹,开发者需要根据具体的竞争强度和业务场景,在 CAS其他并发工具 之间做出权衡。





5️⃣ 参考 5

CAS (Compare and Swap) 详解

什么是 CAS?

CAS(Compare and Swap,比较并交换) 是一种无锁的原子操作,用于在多线程环境下实现同步而不使用传统的锁机制。它是现代并发编程的基石之一。

CAS 操作原理

CAS 操作包含三个基本操作数:

  • 内存位置(V)
  • 期望的原值(A)
  • 新值(B)

操作过程:

css 复制代码
如果内存位置 V 的值等于期望值 A,则将该位置的值更新为新值 B
否则,不进行任何操作

CAS 伪代码

cpp 复制代码
bool CAS(int* ptr, int expected, int new_value) {
    if (*ptr == expected) {
        *ptr = new_value;
        return true;
    }
    return false;
}

CAS 的特点

原子性 CAS 操作是硬件级别的原子操作,不会被线程调度机制打断。

无锁编程 CAS 是实现无锁(lock-free)数据结构的基础。

乐观锁机制 采用"先尝试,失败重试"的策略,而不是悲观地获取锁。

Java 中的 CAS 实现

Atomic 类使用 CAS

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        int oldValue;
        int newValue;
        do {
            oldValue = count.get();        // 读取当前值
            newValue = oldValue + 1;       // 计算新值
        } while (!count.compareAndSet(oldValue, newValue)); // CAS 操作
    }
    
    public int getCount() {
        return count.get();
    }
}

更简洁的写法

java 复制代码
public void incrementSimple() {
    count.incrementAndGet(); // 内部使用 CAS
}

CAS 的典型应用

1. 计数器

java 复制代码
AtomicInteger counter = new AtomicInteger(0);

// 线程安全的自增
counter.incrementAndGet();

// 线程安全的加法
counter.addAndGet(5);

2. 标志位控制

java 复制代码
AtomicBoolean flag = new AtomicBoolean(false);

// 只有一个线程能成功设置为 true
if (flag.compareAndSet(false, true)) {
    // 执行初始化操作
}

3. 引用类型原子更新

java 复制代码
AtomicReference<String> reference = new AtomicReference<>("initial");

// 原子更新引用
reference.compareAndSet("initial", "updated");

CAS 的优缺点

优点

  1. 高性能:避免线程阻塞和上下文切换
  2. 无死锁:不会产生死锁问题
  3. 可扩展性:在高并发场景下性能更好

缺点

  1. ABA 问题:值从 A 变为 B 又变回 A,CAS 会误认为没有变化
  2. 循环时间长开销大:竞争激烈时可能长时间自旋
  3. 只能保证一个变量的原子性:不能同时保证多个变量的原子性

解决 ABA 问题

版本号机制

java 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABASolution {
    private AtomicStampedReference<Integer> atomicRef = 
        new AtomicStampedReference<>(0, 0);
    
    public void update(int expectedValue, int newValue) {
        int[] stampHolder = new int[1];
        int currentValue = atomicRef.get(stampHolder);
        int currentStamp = stampHolder[0];
        
        if (currentValue == expectedValue) {
            atomicRef.compareAndSet(currentValue, newValue, 
                                  currentStamp, currentStamp + 1);
        }
    }
}

CAS 底层实现

CPU 硬件支持

  • x86: CMPXCHG 指令
  • ARM: LDREX/STREX 指令对

Java 中的实现

java 复制代码
// Unsafe 类提供 CAS 操作
public final class Unsafe {
    public final native boolean compareAndSwapObject(
        Object o, long offset, Object expected, Object x);
    
    public final native boolean compareAndSwapInt(
        Object o, long offset, int expected, int x);
    
    public final native boolean compareAndSwapLong(
        Object o, long offset, long expected, long x);
}

实际应用示例

无锁栈实现

java 复制代码
import java.util.concurrent.atomic.AtomicReference;

public class LockFreeStack<T> {
    private static class Node<T> {
        final T value;
        Node<T> next;
        
        Node(T value) {
            this.value = value;
        }
    }
    
    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;
    }
}

性能考虑

适用场景

  • 低到中等竞争程度
  • 操作简单快速
  • 需要高并发性能

不适用场景

  • 高竞争环境(可能导致大量重试)
  • 复杂操作(CAS 不适合复杂业务逻辑)
  • 需要多个变量原子更新的场景

CAS 是现代并发编程的重要工具,理解其原理和适用场景对于编写高性能的并发程序至关重要。





6️⃣ 参考 6

CAS (Compare and Swap) 详解

什么是CAS

CAS(Compare and Swap,比较并交换)是一种无锁(lock-free)的并发控制机制,用于在多线程环境中实现原子操作。它是现代并发编程中重要的同步原语之一。

CAS的基本原理

CAS操作包含三个基本操作数:

  1. 内存位置(V):需要读写的内存位置
  2. 预期原值(A):预期内存位置中的原始值
  3. 新值(B):要写入的新值

CAS的语义是:只有当内存位置V的值等于预期原值A时,才会将V的值更新为新值B,否则不执行任何操作。无论是否更新成功,都会返回当前V的实际值。

CAS的工作流程

  1. 读取内存位置V的当前值
  2. 比较V的当前值与预期值A
    • 如果相等,将V的值更新为B
    • 如果不相等,不执行更新
  3. 返回V的当前值(无论是否更新成功)

CAS的伪代码表示

arduino 复制代码
function compareAndSwap(V, A, B):
    if V == A:
        V = B
        return true
    else:
        return false

CAS的优点

  1. 无锁性:避免了传统锁带来的线程阻塞和上下文切换开销
  2. 高性能:在低竞争场景下性能优于基于锁的同步
  3. 可伸缩性:随着CPU核心数的增加,性能不会显著下降
  4. 避免死锁:由于不使用锁,自然不会发生死锁

CAS的缺点

  1. ABA问题:值从A变为B又变回A,CAS会误认为没有变化
  2. 自旋开销:高竞争场景下,线程会不断重试CAS操作,消耗CPU资源
  3. 只能保证一个共享变量的原子性:对于多个共享变量的操作,需要额外的同步机制

ABA问题及解决方案

ABA问题示例

  1. 线程1读取值A
  2. 线程2将值从A改为B,然后又改回A
  3. 线程1执行CAS时,发现值仍是A,认为没有被修改过

解决方案

  1. 版本号/时间戳 :为共享变量添加版本号,每次修改都递增版本号
    • Java中的AtomicStampedReference
  2. 唯一标识 :使用不可重复的唯一标识代替简单值
    • Java中的AtomicMarkableReference

Java中的CAS实现

Java提供了java.util.concurrent.atomic包来支持CAS操作:

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    private AtomicInteger counter = new AtomicInteger(0);
    
    public void increment() {
        int oldValue;
        int newValue;
        do {
            oldValue = counter.get();
            newValue = oldValue + 1;
        } while (!counter.compareAndSet(oldValue, newValue));
    }
    
    public int get() {
        return counter.get();
    }
}

实际应用场景

  1. 计数器实现:如上述AtomicInteger示例
  2. 无锁数据结构:无锁队列、无锁栈等
  3. 并发集合:ConcurrentHashMap等
  4. 线程安全对象:如AtomicReference等

与锁的比较

特性 CAS 传统锁
性能 低竞争时高,高竞争时可能下降 高竞争时性能下降明显
实现复杂度 较高 较低
死锁风险
适用场景 低竞争、短临界区 高竞争、长临界区

现代CPU对CAS的支持

大多数现代CPU都提供了CAS指令:

  • x86: CMPXCHG (Compare and Exchange)
  • ARM: LDREX/STREX指令对
  • PowerPC: lwarx/stwcx指令对

这些硬件指令保证了CAS操作的原子性。

总结

CAS是一种强大的无锁同步机制,适用于低竞争场景下的并发编程。它通过硬件指令保证原子性,避免了传统锁的开销,但需要开发者注意ABA问题和自旋开销等问题。在现代并发编程中,CAS是构建高性能、可伸缩并发应用的重要工具。





7️⃣ 参考 7

CAS(Compare and Swap)详细解释

  1. 基本定义与原理 CAS(比较并交换)是一种无锁并发控制技术,用于实现多线程环境下的原子操作。其核心包含三个操作数:
  • 内存位置(V):要修改的共享变量地址
  • 预期原值(A):线程读取该内存位置时的旧值
  • 更新值(B):希望写入的新值

操作逻辑

plaintext 复制代码
if V == A then
    V = B   // 更新成功
else
    重试或放弃  // 更新失败

该操作由单条CPU指令 (如x86的CMPXCHG)原子性完成,确保比较和更新过程不可分割。

  1. 工作流程 以两个线程竞争修改共享变量V=10为例:

  2. 线程1和线程2同时读取V=10

  3. 线程1执行CAS(V, 10, 20)

    • 比较发现V==10(匹配)→ 更新V=20成功
  4. 线程2执行CAS(V, 10, 30)

    • 此时V=20≠10(不匹配)→ 更新失败 → 重新读取V=20并自旋重试
  5. 核心特点

优势 劣势
无锁算法:避免线程阻塞和上下文切换开销 ABA问题:值从A→B→A变化时,CAS误判未修改
乐观锁:假设冲突概率低,直接尝试更新 自旋消耗CPU:竞争激烈时循环重试浪费资源
原子性:硬件级指令保证操作不可分割 仅适用简单操作:复杂逻辑需配合其他机制
  1. 典型应用场景
  • 原子类实现 :如Java的AtomicInteger,通过CAS实现incrementAndGet()

    java 复制代码
    // 伪代码实现
    do {
        int oldValue = get();  // 读取当前值
        int newValue = oldValue + 1;
    } while (!CAS(oldValue, newValue));  // 自旋直到成功
  • 无锁数据结构:并发队列(如Disruptor RingBuffer)、并发栈

  • 同步机制基础:实现自旋锁、信号量等

  1. ABA问题解决方案 通过添加版本号/时间戳扩展CAS操作:
  • 原始值:A

  • 扩展结构:(A, Version=1)

  • 更新时同时校验值和版本号:

    plaintext 复制代码
    if (V.value == A && V.version == 1) 
        then update to (B, 2)

    例如Java的AtomicStampedReference


相关问题

  1. CAS操作在哪些场景下性能优于传统锁机制?
  2. 如何通过Java代码实现一个基于CAS的自旋锁?
  3. ABA问题在实际系统中可能引发哪些具体风险?请举例说明
  4. 对比CAS与volatile关键字在并发控制中的异同
  5. 现代CPU如何通过硬件指令(如LL/SC)实现CAS的原子性?




8️⃣ 参考 8

CAS(Compare and Swap,比较并交换)是一种无锁同步机制,核心是通过硬件指令保证操作的原子性,实现多线程下对共享变量的安全修改,无需依赖锁。

一、CAS的核心工作原理 CAS操作依赖三个关键参数,通过"比较-交换"的原子步骤完成变量更新,具体流程如下:

  1. 确定三个核心参数
    • 内存地址V:要修改的共享变量在内存中的地址。
    • 预期值A:线程读取到的变量当前值(修改前的"旧值")。
    • 新值B:线程希望将变量更新为的目标值。
  2. 执行原子操作
    • 硬件层面(如CPU的cmpxchg指令)原子性地比较"内存地址V中的实际值"与"预期值A"。
    • 若两者相等:说明变量未被其他线程修改,将"新值B"写入内存地址V,操作成功。
    • 若两者不相等:说明变量已被其他线程修改,放弃写入,操作失败(线程通常会重试或直接返回)。

二、CAS的关键特性(优缺点) CAS通过无锁设计解决了传统锁的开销问题,但也存在固有局限,具体如下:

  1. 优点
  • 无锁高效:无需加锁/解锁,避免了线程阻塞、上下文切换的开销,在低并发场景下性能优于锁机制。
  • 原子性保障 :依赖CPU硬件指令(如x86的cmpxchg、ARM的ldrex/strex),天然保证"比较-交换"步骤的原子性,无需额外同步。
  • 非阻塞:失败的线程不会阻塞,可选择重试或直接退出,不会影响其他线程的执行。
  1. 缺点
  • ABA问题:变量值从A被改为B,又改回A,CAS会误认为"变量未被修改"而成功更新,可能导致逻辑错误(如链表节点复用场景)。
  • 自旋消耗 :若高并发下变量频繁被修改,CAS会反复重试,持续占用CPU资源(如Java中AtomicIntegergetAndIncrement)。
  • 只能保证单个变量原子性 :CAS仅能对单个共享变量的更新做原子性保障,无法直接处理多个变量的组合原子操作(需结合volatile或其他机制)。

三、ABA问题的解决方案 针对ABA问题,主流方案是为变量增加"版本号"或"时间戳",让变量的"值+版本号"共同作为判断依据,具体实现如下:

  • 核心思路:修改共享变量时,不仅更新值,还同步递增版本号;CAS比较时,同时比较"预期值"和"预期版本号"。
  • 实际案例 :Java中的AtomicStampedReference,通过"对象引用+整数戳"的组合,确保即使值相同,版本号不同也会判定为修改,避免ABA问题。

四、CAS的实际应用场景 CAS是很多并发工具的底层实现,常见应用包括:

  • 原子类 :Java的java.util.concurrent.atomic包(如AtomicIntegerAtomicLong),通过CAS实现变量的原子增减、更新。
  • 并发容器ConcurrentLinkedQueue(无锁队列),通过CAS实现节点的入队、出队操作,避免锁竞争。
  • 锁优化:Synchronized锁的"轻量级锁"阶段,通过CAS尝试将对象头的"Mark Word"设置为线程ID,避免升级为重量级锁。

要不要我帮你整理一份CAS核心知识点脑图?把工作原理、优缺点、解决方案和应用场景按逻辑串联,方便你快速梳理和记忆。





9️⃣ 参考 9

CAS(Compare and Swap,比较并交换)是一种无锁并发编程的核心技术,广泛应用于多线程环境下的原子操作。它通过硬件指令保证操作的原子性,避免了传统锁机制带来的性能开销和死锁风险。


一、CAS 的基本概念

✅ 定义: CAS 是一种原子操作 ,用于实现多线程环境下的无锁更新

✅ 操作逻辑: CAS 操作包含三个参数:

  • 内存地址 V:要更新的变量
  • 预期值 A:当前线程认为 V 的值应该是 A
  • 新值 B:希望将 V 更新为 B

操作语义

text 复制代码
if V == A:
    V = B
    return true
else:
    return false

只有当 V 的当前值等于预期值 A 时,才会将 V 更新为 B,否则不做任何操作。


二、CAS 的底层支持

CAS 操作依赖于CPU 提供的原子指令,例如:

平台 指令
x86 CMPXCHG
ARM LDREX/STREX

这些指令保证了 CAS 操作的原子性,即使在多核环境下也不会出现竞态条件。


三、CAS 的应用场景

✅ 原子类(Java 中的 AtomicInteger , AtomicReference 等)

java 复制代码
AtomicInteger count = new AtomicInteger(0);
count.compareAndSet(0, 1); // 如果当前值是0,则更新为1

✅ 无锁数据结构

  • 无锁队列(Lock-free Queue)
  • 无锁栈(Lock-free Stack)
  • 无锁哈希表(ConcurrentHashMap 的某些实现)

✅ 自旋锁(Spinlock) 使用 CAS 实现简单的自旋锁:

c 复制代码
int lock = 0;

void acquire() {
    while (!CAS(&lock, 0, 1)); // 自旋等待
}

void release() {
    lock = 0;
}

四、CAS 的优点

优点 说明
✅ 无锁 避免死锁、线程阻塞
✅ 高性能 无需内核调度,用户态完成
✅ 可扩展 适合高并发场景

五、CAS 的缺点与问题

❌ 1. ABA 问题 问题描述

线程 T1 读取值为 A,被挂起;线程 T2 将 A → B → A;T1 恢复后认为值没变,CAS 成功,但实际上中间状态已变。

解决方案

  • 版本号机制 (如 Java 的 AtomicStampedReference
  • 指针标记(低 bit 标记版本)

❌ 2. 自旋开销 CAS 失败时会重试,可能导致忙等待(busy waiting),浪费 CPU。

❌ 3. 仅保证单个变量原子性 CAS 只能操作一个变量,无法原子地更新多个变量


六、CAS 与锁的对比

特性 CAS
是否阻塞 ❌ 不阻塞 ✅ 可能阻塞
死锁风险 ❌ 无 ✅ 有
性能 高并发下更好 低并发下稳定
复杂度 高(ABA、重试)
适用范围 原子变量、无锁结构 任意临界区

七、总结一句话

CAS 是一种乐观并发策略,它假设冲突很少发生,失败就重试,避免锁的开销,但需要处理 ABA 和自旋等问题。


✅ 拓展阅读(可选)

  • Java 源码:sun.misc.Unsafe.compareAndSwapInt()
  • C++11:std::atomic<T>::compare_exchange_weak()
  • Linux 内核:cmpxchg() 宏实现

如果你有具体语言(如 Java、C++、Rust)或场景(如队列、计数器)想深入,我可以给你写代码示例。





🔟 参考 10

CAS(Compare and Swap)详细解释

1. 基本定义与原理 CAS(比较并交换)是一种无锁并发控制技术,用于实现多线程环境下的原子操作。其核心包含三个操作数:

  • 内存位置(V):要修改的共享变量地址
  • 预期原值(A):线程读取该内存位置时的旧值
  • 更新值(B):希望写入的新值

操作逻辑

plaintext 复制代码
if V == A then
    V = B   // 更新成功
else
    重试或放弃  // 更新失败

该操作由单条CPU指令 (如x86的CMPXCHG)原子性完成,确保比较和更新过程不可分割。

2. 工作流程 以两个线程竞争修改共享变量V=10为例:

  1. 线程1和线程2同时读取V=10
  2. 线程1执行CAS(V, 10, 20)
    • 比较发现V==10(匹配)→ 更新V=20成功
  3. 线程2执行CAS(V, 10, 30)
    • 此时V=20≠10(不匹配)→ 更新失败 → 重新读取V=20并自旋重试

3. 核心特点

优势 劣势
无锁算法:避免线程阻塞和上下文切换开销 ABA问题:值从A→B→A变化时,CAS误判未修改
乐观锁:假设冲突概率低,直接尝试更新 自旋消耗CPU:竞争激烈时循环重试浪费资源
原子性:硬件级指令保证操作不可分割 仅适用简单操作:复杂逻辑需配合其他机制

4. 典型应用场景

  • 原子类实现 :如Java的AtomicInteger,通过CAS实现incrementAndGet()

    java 复制代码
    // 伪代码实现
    do {
        int oldValue = get();  // 读取当前值
        int newValue = oldValue + 1;
    } while (!CAS(oldValue, newValue));  // 自旋直到成功
  • 无锁数据结构:并发队列(如Disruptor RingBuffer)、并发栈

  • 同步机制基础:实现自旋锁、信号量等

5. ABA问题解决方案 通过添加版本号/时间戳扩展CAS操作:

  • 原始值:A

  • 扩展结构:(A, Version=1)

  • 更新时同时校验值和版本号:

    plaintext 复制代码
    if (V.value == A && V.version == 1) 
        then update to (B, 2)

    例如Java的AtomicStampedReference


相关问题

  1. CAS操作在哪些场景下性能优于传统锁机制?
  2. 如何通过Java代码实现一个基于CAS的自旋锁?
  3. ABA问题在实际系统中可能引发哪些具体风险?请举例说明
  4. 对比CAS与volatile关键字在并发控制中的异同
  5. 现代CPU如何通过硬件指令(如LL/SC)实现CAS的原子性?




🤔 参考 11

CAS(Compare-And-Swap,比较并交换)是一种非常重要的无锁(Lock-Free)并发编程技术,它通过硬件原子指令实现多线程环境下的安全数据操作,避免了传统锁机制的开销。

下面是一个核心概览表,帮你快速了解CAS:

方面 核心要点
核心思想 我认为内存位置 V 的值应该是 A。如果是,我就把它更新为 B;如果不是,我就不更新(通常会重试)。整个过程是原子的。
操作数 内存位置 (V)、预期原值 (A)、新值 (B)
底层支撑 CPU硬件指令(如x86的 CMPXCHG
本质 一种乐观锁实现
关键优点 无锁,减少线程阻塞和上下文切换,在高并发低竞争场景下性能优异
经典问题 ABA问题 (可通过AtomicStampedReference带版本号解决)
常见应用 AtomicInteger等原子类、自旋锁、无锁队列、乐观锁机制

🔧 工作原理与步骤

CAS操作是一个原子指令,其执行流程如下:

  1. 读取(Read) :读取内存位置 V 的当前值,作为预期值 A
  2. 比较(Compare) :比较内存位置 V 的当前值是否仍等于预期值 A
    • 如果相等,说明从读取到现在,没有其他线程修改过此值。
    • 如果不相等,说明值已被其他线程修改,操作失败。
  3. 交换(Swap) :只有在比较成功时,才会将内存位置 V 的值更新为新值 B
  4. 结果返回 :操作会返回是否成功的状态。失败后,常见的策略是自旋(Spin),即循环重试直到成功。

其基本流程可概括为:

plaintext 复制代码
读取V的值 -> 比较V的值是否等于A -> [等于] 则将V的值更新为B -> 返回成功
                             -> [不等于] 返回失败(通常伴随重试)

⚙️ 在Java中的实现

Java主要通过 sun.misc.Unsafe 类(底层调用Native方法)和 java.util.concurrent.atomic 包下的原子类 (如 AtomicInteger, AtomicLong, AtomicReference)来提供CAS操作。

例如 AtomicIntegerincrementAndGet() 方法底层实现:

java 复制代码
public final int incrementAndGet() {
    int current;
    do {
        current = get(); // 获取当前值
        // 循环尝试CAS,直到成功为止
    } while (!compareAndSet(current, current + 1)); 
    return current + 1;
}

AtomicInteger 内部使用了一个 volatile 修饰的 value 字段来保证可见性,并通过 Unsafe.compareAndSwapInt 方法实现CAS操作,该方法参数包括对象实例、字段内存偏移量、预期值和新值。

💡 CAS的优劣势

  • 优点
    • 高性能 :避免了重量级锁带来的线程阻塞、上下文切换和内核态切换的开销,在低竞争度的并发场景下性能显著优于锁。
    • 无死锁:由于线程不会阻塞,从根本上避免了死锁问题(但可能活锁)。
  • 缺点与挑战
    • ABA问题 :这是CAS的一个典型陷阱。假设变量值原来是A,被另一个线程先改为B后又改回A,那么CAS检查时会误以为它从未被修改过。解决方案是为数据加上版本号或时间戳 (如 AtomicStampedReference)。
    • 自旋开销 :在高竞争环境下,如果线程长时间CAS失败不停重试,会空耗CPU资源。。
    • 单变量原子性 :CAS通常只能保证一个共享变量的原子操作。要对多个变量进行原子更新,需使用锁或将多个变量合并到一个对象中,再用 AtomicReference 来引用。

🎯 主要应用场景

  1. 原子类 :Java中的 AtomicInteger, AtomicLong 等原子类,其核心实现依赖于CAS。
  2. 自旋锁(SpinLock):线程通过循环CAS尝试获取锁,避免立即进入阻塞状态。
  3. 无锁数据结构 :如无锁队列(ConcurrentLinkedQueue)、无锁栈等,利用CAS实现线程安全的插入和删除。
  4. 乐观锁机制:数据库或缓存系统中的乐观锁,通常基于版本号(Version)和CAS思想实现。

⚠️ 使用注意事项

  1. 竞争程度 :CAS适用于低到中度线程竞争 的场景。若竞争非常激烈,自旋开销可能很大,此时传统锁可能是更好选择。。
  2. ABA问题不可忽视 :只要值会发生循环变化,就需使用 AtomicStampedReference 等带版本号的原子类来规避风险。
  3. 复杂度权衡 :无锁编程的正确性难以保证,设计复杂。除非确有必要,否则优先选择高级并发工具(如 java.util.concurrent 包中的类),而非直接操作CAS。

💎 总结

CAS是理解现代高并发编程的基石。它通过硬件辅助的原子操作,巧妙地避免了锁的诸多开销,在特定场景下能带来巨大的性能提升。

理解CAS的核心在于掌握其乐观并发 的思想:先冒险操作,遇到冲突再重试。同时,务必警惕其著名的 ABA问题

希望这份详解对你有帮助。

如果你想了解特定并发场景下是选择CAS还是锁更合适,或者想深入了解无锁数据结构的设计,我很乐意继续与你探讨。





🤔 参考 12

CAS(Compare and Swap,比较并交换)是一种无锁并发编程的核心机制,通过硬件指令实现原子操作,确保多线程环境下共享数据的一致性。以下是其核心原理、应用场景及局限性的综合分析:


一、CAS 的核心原理

  1. 操作逻辑

    CAS 包含三个参数:内存地址(V)、预期值(A)、新值(B)。其执行流程为:

    • 比较:读取内存地址 V 的当前值,与预期值 A 比较。
    • 交换:若相等,则将 V 的值更新为 B;否则不修改,返回当前值。
    • 原子性 :整个过程由硬件指令(如 x86 的 cmpxchg)保证,不可被中断。
  2. 硬件支持

    • CPU 指令 :x86 架构的 cmpxchg、ARM 的 ldrex/strex 等指令直接实现 CAS,通过锁总线或缓存一致性协议(如 MESI)保证原子性。
    • Java 实现 :通过 sun.misc.Unsafe 类的 compareAndSwapInt 等方法调用底层指令,原子类(如 AtomicInteger)封装了这些操作。

二、CAS 的优势

  1. 无锁并发

    • 避免传统锁的阻塞和上下文切换开销,适合高并发场景(如计数器、状态标志)。
    • 示例:AtomicInteger.incrementAndGet() 通过自旋 CAS 实现线程安全递增。
  2. 高性能

    • 低冲突时性能远超锁机制(如 synchronized),减少线程阻塞和调度成本。
    • 适用于细粒度操作(如单个变量更新),避免锁的粗粒度限制。
  3. 避免死锁

    • 无锁设计天然规避死锁风险,适用于复杂并发控制场景。

三、CAS 的局限性及解决方案

  1. ABA 问题

    • 现象:变量值从 A → B → A,CAS 误判为未修改,导致逻辑错误。
    • 解决方案
      • 版本号机制 :如 AtomicStampedReference,通过版本号跟踪变量状态。
      • 标记位 :使用 AtomicMarkableReference 标记变量是否被修改。
  2. 自旋开销

    • 问题:高竞争时频繁自旋重试,浪费 CPU 资源。
    • 优化
      • 限制重试次数或结合退避策略(如 Thread.yield())。
      • 自适应自旋(如 JDK 锁优化中的动态调整)。
  3. 单一变量限制

    • 问题:仅支持单个变量的原子操作,多变量需额外处理。
    • 方案
      • 封装多个变量为对象,通过 AtomicReference 操作整体。
      • 复杂场景结合锁机制(如 ConcurrentHashMap 的分段锁)。

四、CAS 的应用场景

  1. 原子计数器

    • AtomicInteger 实现线程安全计数,避免锁的开销。
  2. 无锁数据结构

    • 无锁队列(ConcurrentLinkedQueue)、栈等,通过 CAS 实现并发安全。
  3. 状态标志更新

    • AtomicBoolean 控制线程任务启停,确保状态一致性。
  4. 乐观锁机制

    • 数据库版本号控制、分布式锁(如 Redis 的 SETNX)均基于 CAS 思想。

五、CAS 与锁的对比

特性 CAS(乐观锁) 锁(悲观锁)
核心思想 假设无冲突,失败后重试 假设必有冲突,先加锁再操作
线程状态 无阻塞,自旋等待 阻塞等待锁释放
性能 低冲突时高效,高冲突时性能下降 高冲突时稳定,低冲突时开销大
适用场景 读多写少、操作简单 写多读少、临界区复杂
ABA 问题 存在(需额外解决) 不存在

六、总结 CAS 是无锁并发的基石,通过硬件指令实现原子操作,在低冲突场景下显著提升性能。但其局限性(如 ABA 问题、自旋开销)需结合版本号、对象封装等方案解决。实际开发中,应根据场景选择:低竞争用 CAS,高竞争用锁 ,例如 JDK 的 LongAdder 在高并发下分段累加,平衡性能与准确性。





🤔 参考 13

CAS(Compare and Swap,比较并交换)是并发编程中一种非常重要的无锁(Lock-Free)原子操作,它被广泛应用于实现多线程同步(如实现自旋锁、乐观锁)和构建非阻塞数据结构。下面我将从多个角度为你详细解析它。


一、核心思想与原理

核心思想: "我认为值应该是A,如果是,我就把它改成B;如果不是,说明有别人改过了,那我就不修改了。"

这是一个原子操作,意味着比较和交换这两个步骤在执行时是不可分割的,中间不会被打断。

操作数: CAS 操作通常需要三个参数:

  1. 内存位置(V):要操作的内存地址(例如,一个共享变量的地址)。
  2. 期望原值(A) :线程认为该内存位置当前应该具有的值
  3. 新值(B) :如果期望值匹配,线程希望将该内存位置更新为的新值

伪代码逻辑:

cpp 复制代码
function CAS(memory_address, expected_value, new_value) {
    if (*memory_address == expected_value) {
        *memory_address = new_value;
        return true; // 成功
    } else {
        return false; // 失败
    }
}

(注意:实际的CPU指令将整个if判断和赋值作为一个不可中断的指令执行)


二、底层实现

CAS 的实现高度依赖于硬件。现代大多数CPU架构(如 x86、ARM)都提供了实现CAS的指令:

  • x86: CMPXCHG (Compare and Exchange) 指令。
  • ARM: LDREXSTREX 指令组合。

高级编程语言(如 Java、C++)通过调用这些底层硬件指令来提供CAS操作。

  • Java:sun.misc.Unsafe 类中有 compareAndSwapInt, compareAndSwapLong 等方法。但我们通常不直接使用它,而是使用封装好的原子类,如 AtomicInteger, AtomicReference 等。
  • C++11: 在 `` 头文件中提供了 std::atomic 及其相关的 compare_exchange_strongcompare_exchange_weak 成员函数。

三、一个简单的Java示例

Java中的 AtomicInteger 是使用CAS的经典例子。

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(0); // 初始值为0

        // 线程1:尝试将 0 -> 1
        boolean success1 = atomicInt.compareAndSet(0, 1); // CAS操作
        System.out.println("CAS(0, 1): " + success1 + ", value: " + atomicInt.get());

        // 线程2:尝试将 0 -> 2 (但此时值已经是1了,所以会失败)
        boolean success2 = atomicInt.compareAndSet(0, 2); // CAS操作
        System.out.println("CAS(0, 2): " + success2 + ", value: " + atomicInt.get());
    }
}

输出:

objectivec 复制代码
CAS(0, 1): true, value: 1
CAS(0, 2): false, value: 1

这个例子清晰地展示了CAS的语义:只有在值符合预期时才会修改成功。


四、优点与应用场景

  1. 性能优势( vs. 锁 )

    • 减少开销:避免了操作系统层面线程挂起和上下文切换的巨大开销。
    • 避免死锁:由于不使用锁,从根本上避免了死锁问题。
    • 非阻塞:一个线程的失败不会导致其他线程阻塞,它们可以立即重试或进行其他操作。
  2. 经典应用场景

    • 原子变量AtomicInteger, AtomicLong, AtomicReference 等。
    • 非阻塞数据结构:无锁队列(Lock-Free Queue)、无锁栈(Lock-Free Stack)等高性能并发容器。
    • 乐观锁:在数据库和并发控制中,CAS是实现乐观锁的核心思想。例如,更新数据前先比较版本号。

五、经典问题与局限性

  1. ABA 问题

    • 描述:线程1读到内存值V为A,准备执行CAS(A->B)。此时线程2将值从A改为C,然后又改回A。线程1执行CAS时,发现值还是A,于是误以为没有变化,操作成功。但这中间可能已经发生了其他有意义的变化(比如链表结构已变)。
    • 解决方案 :使用带版本号的原子引用 (Stamp)。每次修改不仅更新值,还递增一个版本号。CAS时同时比较值和版本号。Java中的 AtomicStampedReference 就是为此而生的。
  2. 循环时间长开销大

    • 描述:如果CAS操作一直失败,线程会不断自旋重试(Spin),如果竞争激烈,会长时间占用CPU资源。
    • 解决方案:限制自旋次数,或与其他同步机制结合使用(例如,在Java中,JVM会管理自旋锁的自旋次数)。
  3. 只能保证一个共享变量的原子操作

    • 描述:一个CAS操作只能针对一个变量。对多个共享变量操作时,CAS无法保证其原子性。
    • 解决方案 :可以将多个变量合并到一个对象中,然后使用 AtomicReference 来保证这个对象引用的原子性。

总结

特性 描述
核心 无锁的原子操作,通过硬件指令保证"比较-交换"的原子性。
优点 高性能、非阻塞、避免死锁。
缺点 ABA问题、自旋CPU开销、只能保证一个变量的原子性。
应用 原子类、乐观锁、无锁数据结构。
本质 一种乐观并发策略,假设冲突很少发生,发生冲突后再重试。

CAS是现代并发编程的基石之一,它使得我们能够在不使用重量级锁的情况下,安全地管理共享数据,极大地提升了多线程程序的性能和可伸缩性。理解CAS是深入理解Java JUC(java.util.concurrent)包或其他语言并发库的关键。





🤔 参考 14

以下是 CAS(Compare and Swap)详解:

一、核心概念与原理

  1. 基本定义

    • 本质:CAS是一种由CPU硬件提供的原子化同步原语,用于实现多线程环境下的无锁同步机制[^1^][^2^]。
    • 操作三要素
      • 内存位置(V):目标变量所在的内存地址;
      • 预期原值(A):期望的旧值;
      • 新值(B):拟更新的值[^1^][^2^][^3^]。
    • 核心语义:"若V的值等于A,则将其更新为B,否则不修改并返回当前值"。这一过程在硬件层面保证原子性[^1^][^2^][^3^]。
  2. 乐观锁特性

    • 无锁设计:与传统互斥锁不同,CAS不阻塞线程,而是通过"尝试-失败重试"机制乐观地假设冲突较少[^2^][^3^]。
    • 非阻塞优势:避免了线程上下文切换和挂起带来的性能损耗,适用于低竞争场景[^3^][^5^]。

二、底层实现与技术支撑

  1. 硬件基础

    • CPU指令集 :不同架构提供专用指令实现CAS的原子性:
      • x86:cmpxchg指令;
      • ARM:LDREX/STREX指令对[^1^][^3^][^5^]。
    • 原子性保障:通过读-改-写操作的组合指令一次性完成,确保操作不可中断[^1^][^3^]。
  2. Java中的封装

    • Unsafe类 :Java通过sun.misc.Unsafe类的本地方法(如compareAndSwapInt())直接调用CPU指令[^1^][^2^][^3^]。
    • 间接调用 :由于Unsafe类限制访问,通常通过JUC包中的原子类(如AtomicInteger)间接使用CAS能力[^1^][^2^][^3^]。

三、典型应用场景

  1. 高性能计数器

    • 实现方式 :利用AtomicIntegerincrementAndGet()方法,通过自旋+CAS循环实现线程安全递增[^2^][^3^][^4^]。
    • 优势:避免锁竞争,显著提升并发性能[^3^][^5^]。
  2. 自定义自旋锁

    • 核心逻辑:通过CAS尝试获取锁(如将状态从0改为1),若失败则持续自旋等待[^2^][^4^][^5^]。
    • 示例SpinLock类中通过AtomicReference<Thread>存储持有者信息[^2^][^4^]。
  3. 无锁数据结构

    • 典型应用:构建非阻塞队列、栈等数据结构,依赖CAS保证节点操作的原子性[^3^][^5^]。

四、关键问题与解决方案

  1. ABA问题

    • 现象:变量从A→B→A变化后,CAS仍认为未改变,可能导致逻辑错误[^1^][^3^]。
    • 解决方案
      • 版本号机制:结合次数或时间戳判断中间是否发生变更[^3^][^5^];
      • AtomicStampedReference:Java提供的带版本控制的引用类[^1^][^3^]。
  2. 自旋开销与CPU消耗

    • 问题:高竞争下频繁重试导致CPU资源浪费[^3^][^5^]。
    • 优化策略
      • 限制重试次数:避免无限循环[^3^][^5^];
      • 引入暂停 :使用Thread.yield()LockSupport.parkNanos()让出CPU[^3^][^5^]。
  3. 单变量原子性限制

    • 局限:CAS仅能保证单个变量的原子性,多变量操作需额外手段[^3^][^5^]。
    • 扩展方案 :使用AtomicReference包装复合对象,或改用互斥锁[^3^][^5^]。

五、CAS与其他同步机制对比

特性 CAS Synchronized/Mutex
阻塞行为 无阻塞(忙等待) 阻塞(线程挂起)
性能 低竞争场景更优 高竞争场景更优
CPU消耗 高竞争时较高 上下文切换开销大
死锁风险 存在
适用场景 简单操作、低竞争 复杂临界区、高竞争

综上所述,CAS作为一种轻量级的无锁同步机制,通过硬件原子指令实现了高效的并发控制。其在低竞争场景下性能优异,但也需注意ABA问题、自旋开销及单变量限制等陷阱。合理选择CAS与互斥锁,需根据实际场景权衡性能与复杂度。





🤔 参考 15

CAS(Compare and Swap),即"比较并交换",是一种用于实现多线程同步的原子操作,常用于无锁(lock-free)编程中,是实现线程安全操作的重要机制之一。

一、CAS 的基本原理

CAS 操作包含三个操作数:

  • 内存位置(V):要读写的内存变量。
  • 预期值(A):线程认为该变量当前应该具有的值。
  • 新值(B):线程希望将变量更新为的值。

CAS 的执行逻辑如下:

如果内存位置 V 的当前值等于预期值 A,那么将内存位置 V 的值更新为新值 B;否则不做任何操作。整个操作是原子的,不会被其他线程中断。

用伪代码表示如下:

java 复制代码
boolean compareAndSwap(int *V, int A, int B) {
    if (*V == A) {
        *V = B;
        return true; // 成功更新
    } else {
        return false; // 值已被修改,更新失败
    }
}

二、CAS 的应用场景

  1. 原子变量(如 AtomicInteger) : Java 中的 java.util.concurrent.atomic 包提供了基于 CAS 实现的原子类,例如 AtomicIntegerAtomicLongAtomicReference 等。

  2. 无锁队列/栈: 利用 CAS 实现线程安全的队列、栈等数据结构,避免使用传统锁机制,提高并发性能。

  3. 自旋锁(SpinLock): 基于 CAS 实现的一种轻量级锁机制,适用于锁竞争不激烈的场景。

三、CAS 的优点

  • 性能高:相比使用锁机制,CAS 避免了线程阻塞和上下文切换的开销。
  • 无死锁:不使用锁,因此不会出现死锁问题。

四、CAS 的缺点

  1. ABA 问题: 如果变量 V 的值从 A 被修改为 B,再改回 A,CAS 会误认为它从未被修改过。

    解决方案 : 使用版本号(如 Java 中的 AtomicStampedReference)或时间戳来区分不同状态。

  2. 只能保证一个变量的原子性 : 如果需要对多个变量进行原子操作,需要额外封装(如使用 AtomicReference 包装多个变量)。

  3. 自旋开销大: 如果 CAS 多次失败,线程会不断重试(自旋),造成 CPU 资源浪费。

五、Java 中的实现示例

AtomicInteger 为例:

java 复制代码
AtomicInteger atomicInt = new AtomicInteger(0);

// 使用 CAS 实现递增
int oldValue = atomicInt.get();
while (!atomicInt.compareAndSet(oldValue, oldValue + 1)) {
    oldValue = atomicInt.get();
}

六、总结

CAS 是一种高效的并发控制机制,适用于读多写少、竞争不激烈的场景。但需注意 ABA 问题、只能操作单个变量等局限性。现代并发编程中,CAS 是构建高性能、无锁数据结构的基础。





CAS 的三大经典问题

CAS 的三大经典问题

1. ABA 问题

问题描述 ABA 问题是 CAS 操作中最经典的问题。它发生在以下情况:

  • 线程1读取内存位置 V 的值为 A
  • 线程2将值从 A 改为 B,然后又改回 A
  • 线程1执行 CAS 操作,发现值仍然是 A,于是操作成功

虽然最终值相同,但中间经历了状态变化,可能导致逻辑错误。

实际场景示例

java 复制代码
public class ABAProblem {
    private AtomicReference<String> stackTop = new AtomicReference<>("A");
    
    public void push(String value) {
        String oldTop;
        do {
            oldTop = stackTop.get();
            // 模拟其他操作
        } while (!stackTop.compareAndSet(oldTop, value));
    }
    
    public String pop() {
        String oldTop;
        String newTop;
        do {
            oldTop = stackTop.get();
            if (oldTop == null) return null;
            // 获取下一个节点
            newTop = getNextNode(oldTop);
        } while (!stackTop.compareAndSet(oldTop, newTop));
        return oldTop;
    }
    
    // 线程1:读取栈顶为 "A"
    // 线程2:pop() 将栈顶从 "A" -> "B"
    // 线程2:push("A") 将栈顶从 "B" -> "A"
    // 线程1:CAS 成功,但实际上栈结构已发生变化
}

解决方案

使用 AtomicStampedReference

java 复制代码
public class ABASolution {
    private AtomicStampedReference<String> stackTop = 
        new AtomicStampedReference<>("A", 0);
    
    public boolean push(String value) {
        int[] stampHolder = new int[1];
        String oldTop;
        int oldStamp;
        
        do {
            oldTop = stackTop.get(stampHolder);
            oldStamp = stampHolder[0];
            // 检查是否有其他修改
        } while (!stackTop.compareAndSet(oldTop, value, oldStamp, oldStamp + 1));
        
        return true;
    }
    
    public String pop() {
        int[] stampHolder = new int[1];
        String oldTop;
        int oldStamp;
        String newTop;
        
        do {
            oldTop = stackTop.get(stampHolder);
            oldStamp = stampHolder[0];
            if (oldTop == null) return null;
            newTop = getNextNode(oldTop);
        } while (!stackTop.compareAndSet(oldTop, newTop, oldStamp, oldStamp + 1));
        
        return oldTop;
    }
}

使用 AtomicMarkableReference

java 复制代码
public class ABASolution2 {
    private AtomicMarkableReference<String> stackTop = 
        new AtomicMarkableReference<>("A", false);
    
    public boolean push(String value) {
        boolean[] markHolder = new boolean[1];
        String oldTop;
        
        do {
            oldTop = stackTop.get(markHolder);
            // 使用标记位来检测变化
        } while (!stackTop.compareAndSet(oldTop, value, markHolder[0], !markHolder[0]));
        
        return true;
    }
}

2. 循环时间长开销大

问题描述 在高并发场景下,多个线程同时竞争同一个变量,导致大量线程不断重试 CAS 操作,造成 CPU 资源浪费。

性能影响

java 复制代码
public class HighContentionExample {
    private AtomicInteger counter = new AtomicInteger(0);
    
    public void highContentionMethod() {
        // 在高并发下,这个循环可能导致大量 CPU 消耗
        while (!counter.compareAndSet(counter.get(), counter.get() + 1)) {
            // 空转,消耗 CPU
        }
    }
}

解决方案

  1. 退避策略 (Backoff)
java 复制代码
public class BackoffStrategy {
    private AtomicInteger counter = new AtomicInteger(0);
    private final int maxDelay = 1000; // 最大延迟 1ms
    
    public void incrementWithBackoff() {
        int delay = 1;
        while (true) {
            int current = counter.get();
            int next = current + 1;
            
            if (counter.compareAndSet(current, next)) {
                break; // 成功退出
            }
            
            // 指数退避
            try {
                Thread.sleep(0, delay); // 纳秒级休眠
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
            
            delay = Math.min(delay * 2, maxDelay);
        }
    }
}
  1. 使用 LongAdder (Java 8+)
java 复制代码
import java.util.concurrent.atomic.LongAdder;

public class LowContentionSolution {
    private LongAdder counter = new LongAdder();
    
    public void increment() {
        counter.increment(); // 内部使用分段计数,减少竞争
    }
    
    public long getCount() {
        return counter.sum();
    }
}
  1. 分散热点
java 复制代码
public class HotspotDistribution {
    private final int CONCURRENCY_LEVEL = Runtime.getRuntime().availableProcessors();
    private AtomicInteger[] counters;
    
    public HotspotDistribution() {
        counters = new AtomicInteger[CONCURRENCY_LEVEL];
        for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
            counters[i] = new AtomicInteger(0);
        }
    }
    
    public void increment() {
        int index = Thread.currentThread().hashCode() % CONCURRENCY_LEVEL;
        if (index < 0) index = -index;
        counters[index].incrementAndGet();
    }
    
    public int getTotal() {
        int sum = 0;
        for (AtomicInteger counter : counters) {
            sum += counter.get();
        }
        return sum;
    }
}

3. 只能保证一个共享变量的原子操作

问题描述 CAS 只能保证单个变量的原子性,对于需要同时更新多个相关变量的场景无法保证一致性。

典型场景

java 复制代码
public class MultiVariableProblem {
    private AtomicInteger x = new AtomicInteger(0);
    private AtomicInteger y = new AtomicInteger(0);
    
    // 需要原子性地更新 x 和 y
    public void updateBoth(int newX, int newY) {
        // 这两个操作不是原子的
        x.set(newX);
        y.set(newY);
        
        // 在 set(newX) 和 set(newY) 之间,其他线程可能看到不一致的状态
    }
    
    public void checkConsistency() {
        int currentX = x.get();
        int currentY = y.get();
        // 这里可能看到 x 和 y 的不一致状态
    }
}

解决方案

  1. 使用 AtomicReference 封装对象
java 复制代码
public class MultiVariableSolution1 {
    static class Point {
        final int x;
        final int y;
        
        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
    
    private AtomicReference<Point> point = new AtomicReference<>(new Point(0, 0));
    
    public void updateBoth(int newX, int newY) {
        Point current;
        Point newPoint;
        do {
            current = point.get();
            newPoint = new Point(newX, newY);
        } while (!point.compareAndSet(current, newPoint));
    }
    
    public int getX() {
        return point.get().x;
    }
    
    public int getY() {
        return point.get().y;
    }
}
  1. 使用锁机制
java 复制代码
public class MultiVariableSolution2 {
    private int x = 0;
    private int y = 0;
    private final Object lock = new Object();
    
    public void updateBoth(int newX, int newY) {
        synchronized(lock) {
            x = newX;
            y = newY;
        }
    }
    
    public void checkConsistency() {
        synchronized(lock) {
            int currentX = x;
            int currentY = y;
            // 保证看到一致的状态
        }
    }
}
  1. 使用 VarHandle (Java 9+)
java 复制代码
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class MultiVariableSolution3 {
    private static class State {
        int x;
        int y;
    }
    
    private volatile State state = new State();
    private static final VarHandle STATE_HANDLE;
    
    static {
        try {
            STATE_HANDLE = MethodHandles.lookup()
                .findVarHandle(MultiVariableSolution3.class, "state", State.class);
        } catch (Exception e) {
            throw new Error(e);
        }
    }
    
    public void updateBoth(int newX, int newY) {
        State current;
        State newState = new State();
        newState.x = newX;
        newState.y = newY;
        
        do {
            current = state;
        } while (!STATE_HANDLE.compareAndSet(this, current, newState));
    }
}

综合解决方案示例

完整的线程安全计数器

java 复制代码
public class CompleteCounter {
    private final LongAdder baseCount = new LongAdder();
    private final AtomicStampedReference<Long> lastUpdate = 
        new AtomicStampedReference<>(0L, 0);
    
    public void increment() {
        baseCount.increment();
        
        // 原子更新最后更新时间戳
        int[] stampHolder = new int[1];
        Long oldValue;
        int oldStamp;
        do {
            oldValue = lastUpdate.get(stampHolder);
            oldStamp = stampHolder[0];
        } while (!lastUpdate.compareAndSet(oldValue, System.currentTimeMillis(), 
                                         oldStamp, oldStamp + 1));
    }
    
    public long getCount() {
        return baseCount.sum();
    }
    
    public long getLastUpdateTime() {
        return lastUpdate.getReference();
    }
}

总结

问题 描述 解决方案
ABA 问题 值从 A→B→A,CAS 误认为无变化 使用版本号(AtomicStampedReference)
循环开销大 高竞争下大量线程空转 退避策略、LongAdder、分散热点
单变量限制 无法保证多变量原子性 对象封装、锁机制、VarHandle

理解这三大问题并选择合适的解决方案,对于编写正确、高效的无锁并发程序至关重要。在实际开发中,应根据具体场景权衡各种方案的优缺点。





CAS 的三大经典问题: 1.ABA 问题 ; 2.循环时间长开销大 ; 3.只能保证一个共享变量的原子操作

好的,CAS(Compare and Swap)虽然强大,但也存在三个经典的棘手问题。下面我们来详细解析这三大经典问题


问题一:ABA 问题

什么是 ABA 问题?

ABA 问题是 CAS 操作中最著名、最经典的问题。它的产生过程如下:

  1. 线程 1 从内存位置 V 读取值为 A
  2. 线程 1 被挂起(时间片用完、调度等)
  3. 线程 2 开始执行,将 V 的值从 A 改为 B
  4. 线程 2 继续执行,又将 V 的值从 B 改回 A
  5. 线程 1 恢复执行,进行 CAS 操作:
    • 检查 V 的值是否还是 A(确实是 A)
    • CAS 操作成功,将值更新为新值

问题在于 :虽然 CAS 在技术上成功了,但 V 的值在期间已经经历了 A → B → A 的变化,这个"中间状态"被线程 1 完全忽略了。

ABA 问题的危害

用一个现实生活中的例子:

  • 你把一瓶水放在桌上,离开了一会
  • 你的同事喝光了这瓶水,然后重新灌满了清水
  • 你回来后,看到还是一瓶"水",以为没人动过,就继续喝

虽然看起来都是"一瓶水",但内容已经发生了变化。

在编程中的具体危害

  • 在链表、栈等数据结构中,节点地址可能被重用,导致数据结构损坏
  • 在版本控制中,无法感知中间的状态变化
  • 在财务系统中,资金可能经历了多次流转后回到原值,但交易历史被忽略

解决方案

1. 添加版本号(Version Stamp) 每次修改都递增一个版本号,CAS 时同时检查值和版本号。

Java 中的实现:AtomicStampedReference

java 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABASolution {
    public static void main(String[] args) {
        // 初始值为 100,版本号为 0
        AtomicStampedReference<Integer> atomicStampedRef = 
            new AtomicStampedReference<>(100, 0);
        
        int oldValue = 100;
        int newValue = 200;
        int oldStamp = atomicStampedRef.getStamp(); // 获取当前版本号
        
        // 线程1:尝试CAS,但被挂起
        // 线程2:修改值 100 -> 150,版本号 0 -> 1
        boolean success2 = atomicStampedRef.compareAndSet(100, 150, 0, 1);
        
        // 线程2:再次修改值 150 -> 100,版本号 1 -> 2
        boolean success3 = atomicStampedRef.compareAndSet(150, 100, 1, 2);
        
        // 线程1:恢复执行,尝试CAS
        // 虽然值还是100,但版本号已经从0变成2,所以CAS失败!
        boolean success1 = atomicStampedRef.compareAndSet(oldValue, newValue, oldStamp, oldStamp + 1);
        
        System.out.println("Thread1 CAS: " + success1); // false
        System.out.println("Current value: " + atomicStampedRef.getReference()); // 100
        System.out.println("Current stamp: " + atomicStampedRef.getStamp()); // 2
    }
}

2. 使用布尔标记 Java 中的 AtomicMarkableReference 用一个布尔值来标记是否被修改过。

java 复制代码
import java.util.concurrent.atomic.AtomicMarkableReference;

public class MarkableSolution {
    public static void main(String[] args) {
        AtomicMarkableReference<Integer> atomicMarkableRef = 
            new AtomicMarkableReference<>(100, false);
        
        // 类似的CAS操作,同时检查值和标记位
        boolean success = atomicMarkableRef.compareAndSet(100, 200, false, true);
    }
}

问题二:循环时间长开销大

问题描述

在高并发环境下,当多个线程同时竞争同一个资源时:

  • 线程执行 CAS 操作失败后,会立即重试(自旋)
  • 如果竞争非常激烈,大量线程会不断循环重试
  • 导致 CPU 资源被大量消耗在无用的循环上

具体表现

java 复制代码
public class SpinProblem {
    private AtomicInteger counter = new AtomicInteger(0);
    
    public void highContentionMethod() {
        // 在高并发环境下,这个循环可能执行很多次
        while (!counter.compareAndSet(counter.get(), counter.get() + 1)) {
            // 空循环,消耗CPU
        }
    }
}

解决方案

1. 引入退避机制 失败后不是立即重试,而是等待一小段时间:

java 复制代码
public class BackoffSolution {
    private AtomicInteger counter = new AtomicInteger(0);
    
    public void casWithBackoff() {
        int backoffTime = 1; // 初始退避时间
        while (true) {
            int current = counter.get();
            int next = current + 1;
            
            if (counter.compareAndSet(current, next)) {
                break; // 成功,退出循环
            } else {
                // 失败,执行退避
                try {
                    Thread.sleep(backoffTime);
                    backoffTime = Math.min(backoffTime * 2, 100); // 指数退避,但有上限
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}

2. 使用 LongAdder(JDK8+) 在超高并发统计场景下,LongAdderAtomicLong 性能更好:

java 复制代码
import java.util.concurrent.atomic.LongAdder;

public class LongAdderSolution {
    private LongAdder counter = new LongAdder();
    
    public void increment() {
        counter.increment(); // 内部使用分段CAS,减少竞争
    }
    
    public long getValue() {
        return counter.sum();
    }
}

3. 切换为锁机制 当检测到竞争过于激烈时,可以降级使用传统的锁:

java 复制代码
public class AdaptiveSolution {
    private AtomicInteger casCounter = new AtomicInteger(0);
    private final Object lock = new Object();
    private int lockCounter = 0;
    private int failureCount = 0;
    
    public void adaptiveIncrement() {
        if (failureCount > 1000) { // 如果失败次数太多,使用锁
            synchronized(lock) {
                lockCounter++;
            }
        } else {
            if (!casCounter.compareAndSet(casCounter.get(), casCounter.get() + 1)) {
                failureCount++;
                // 可能递归调用或其他处理
            }
        }
    }
}

问题三:只能保证一个共享变量的原子操作

问题描述

单个 CAS 指令只能保证一个内存位置 的原子性。如果需要同时原子地更新多个相关变量,CAS 就无能为力了。

典型场景

java 复制代码
public class MultiVariableProblem {
    private AtomicInteger x = new AtomicInteger(0);
    private AtomicInteger y = new AtomicInteger(0);
    
    // 错误:这两个CAS操作不是原子的
    public void updateBoth(int newX, int newY) {
        x.compareAndSet(x.get(), newX);  // 第一步
        y.compareAndSet(y.get(), newY);  // 第二步
        // 在两个CAS操作之间,其他线程可能看到不一致的状态
    }
}

解决方案

1. 使用 AtomicReference 包装对象 将多个变量封装到一个对象中,然后对整个对象进行 CAS:

java 复制代码
import java.util.concurrent.atomic.AtomicReference;

public class MultiVariableSolution {
    static class Point {
        final int x;
        final int y;
        
        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
    
    private AtomicReference<Point> pointRef = 
        new AtomicReference<>(new Point(0, 0));
    
    public void updateBoth(int newX, int newY) {
        Point current, next;
        do {
            current = pointRef.get();
            next = new Point(newX, newY);
        } while (!pointRef.compareAndSet(current, next));
    }
    
    public int getX() {
        return pointRef.get().x;
    }
    
    public int getY() {
        return pointRef.get().y;
    }
}

2. 使用锁机制 对于复杂的多变量原子操作,有时使用锁更简单可靠:

java 复制代码
public class LockSolution {
    private int x = 0;
    private int y = 0;
    private final Object lock = new Object();
    
    public void updateBoth(int newX, int newY) {
        synchronized(lock) {
            x = newX;
            y = newY;
        }
    }
}

3. 使用数组 + AtomicReferenceArray 如果需要原子地更新数组中的多个元素:

java 复制代码
import java.util.concurrent.atomic.AtomicReferenceArray;

public class ArraySolution {
    private AtomicReferenceArray<String> array = 
        new AtomicReferenceArray<>(10);
    
    // 虽然不能原子更新多个元素,但可以原子更新单个元素
    public void updateElement(int index, String newValue) {
        array.set(index, newValue);
    }
}

总结对比

问题 描述 解决方案
ABA 问题 值经历了 A→B→A 变化,CAS 无法感知中间状态 1. AtomicStampedReference(版本号) 2. AtomicMarkableReference(标记位)
循环开销大 高竞争下大量线程自旋,消耗 CPU 1. 退避机制 2. LongAdder 分段 3. 竞争激烈时降级为锁
多变量原子性 单个 CAS 只能保证一个变量的原子性 1. 使用 AtomicReference 包装对象 2. 使用锁 3. 重新设计数据结构

实践建议

  1. 低竞争场景:优先使用 CAS,性能最好
  2. 高竞争场景 :考虑使用 LongAdder 或退避机制
  3. 状态复杂场景 :使用 AtomicStampedReference 或直接使用锁
  4. 需要事务性操作 :考虑将多个变量封装为不可变对象,使用 AtomicReference

理解这三大问题及其解决方案,有助于在实际开发中更好地运用 CAS 技术,在保证线程安全的同时获得最佳性能。





乐观锁 与 悲观锁

我们来详细探讨乐观锁悲观锁这两种重要的并发控制策略。


1. 核心思想对比

悲观锁 (Pessimistic Locking) 思想:假设最坏的情况会发生。"我认为只要我访问数据,别人就很可能也会来修改它,所以我要先加锁,确保只有我能操作。"

比喻

  • 就像你把日记本锁在抽屉里,认为只要拿出来别人就可能会看
  • 就像停车场只有一个出口,车辆必须排队依次通过

乐观锁 (Optimistic Locking) 思想:假设最好的情况会发生。"我认为别人不太会在我操作的时候修改数据,所以我不加锁,先操作,提交时再检查有没有冲突。"

比喻

  • 就像多人编辑在线文档,各自编辑自己的副本,最后合并时解决冲突
  • 就像Git版本控制,各自提交代码,合并时处理冲突

2. 实现机制对比

悲观锁的实现方式

数据库层面:

sql 复制代码
-- 使用 SELECT ... FOR UPDATE 锁定记录
BEGIN TRANSACTION;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 悲观锁
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

Java 层面:

java 复制代码
// 使用 synchronized
public synchronized void transfer(int from, int to, int amount) {
    // 转账操作
}

// 使用 ReentrantLock
private final Lock lock = new ReentrantLock();

public void transfer(int from, int to, int amount) {
    lock.lock();
    try {
        // 转账操作
    } finally {
        lock.unlock();
    }
}

乐观锁的实现方式

数据库层面(版本号机制):

sql 复制代码
-- 添加 version 字段
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 123 AND version = 5; -- 检查版本号

-- 如果受影响行数为0,说明版本号已变化,需要重试

Java 层面(CAS 操作):

java 复制代码
// 使用 AtomicInteger
private AtomicInteger balance = new AtomicInteger(1000);

public boolean withdraw(int amount) {
    int current, next;
    do {
        current = balance.get();
        if (current < amount) {
            return false; // 余额不足
        }
        next = current - amount;
    } while (!balance.compareAndSet(current, next)); // CAS操作
    return true;
}

// 使用 AtomicStampedReference 解决ABA问题
private AtomicStampedReference<Integer> account = 
    new AtomicStampedReference<>(1000, 0);

public boolean transfer(int amount) {
    int[] stampHolder = new int[1];
    int current, next, currentStamp, nextStamp;
    do {
        current = account.get(stampHolder);
        currentStamp = stampHolder[0];
        if (current < amount) return false;
        next = current - amount;
        nextStamp = currentStamp + 1;
    } while (!account.compareAndSet(current, next, currentStamp, nextStamp));
    return true;
}

3. 工作流程对比

悲观锁工作流程

markdown 复制代码
1. 申请锁
   ↓
2. 获取锁成功? → 失败:阻塞等待
   ↓ 成功  
3. 执行业务操作
   ↓
4. 提交事务,释放锁

乐观锁工作流程

markdown 复制代码
1. 读取数据(包括版本号)
   ↓
2. 执行业务逻辑(计算新值)
   ↓
3. 尝试更新(检查版本号)
   ↓
4. 版本一致? → 否:重试或失败处理
   ↓ 是
5. 更新成功

4. 优缺点分析

悲观锁的优点

  • 简单可靠:逻辑直接,容易理解
  • 强一致性:保证数据操作的绝对串行化
  • 避免冲突:从根本上防止并发冲突
  • 适合长事务:对于执行时间长的操作很有效

悲观锁的缺点

  • 性能开销:加锁、释放锁需要额外开销
  • 死锁风险:可能产生死锁,需要额外处理
  • 并发度低:阻塞其他线程,降低系统吞吐量
  • ❌ ** scalability 差**:不适合高并发场景

乐观锁的优点

  • 高并发:读操作不会被阻塞,提高吞吐量
  • 无死锁:不使用锁,避免死锁问题
  • 性能好:在低冲突场景下性能优异
  • 适合读多写少:大多数场景都是读多写少

乐观锁的缺点

  • ABA 问题:需要额外机制解决
  • 高冲突时性能差:大量重试消耗 CPU
  • 实现复杂:需要处理重试逻辑和冲突解决
  • 不适合长事务:重试次数可能很多

5. 适用场景

适合使用悲观锁的场景

  1. 写操作频繁:冲突发生概率很高的场景
  2. 临界区复杂:操作执行时间较长
  3. 数据一致性要求极高:不允许任何冲突发生
  4. 简单业务逻辑:不想处理复杂的重试机制

具体例子:

  • 银行核心系统的账户余额修改
  • 库存管理中稀缺商品的抢购
  • 票务系统的座位锁定

适合使用乐观锁的场景

  1. 读多写少:大部分操作是读取,很少修改
  2. 低冲突环境:并发写入的概率较低
  3. 高性能要求:需要支持高并发访问
  4. 短事务:业务操作执行时间短

具体例子:

  • 社交媒体的点赞计数
  • 文章的阅读量统计
  • 用户个人信息的更新
  • 购物车的商品数量修改

6. 实战示例

库存管理的两种实现

悲观锁实现:

java 复制代码
@Service
public class InventoryServicePessimistic {
    
    @Transactional
    public boolean deductStock(Long productId, Integer quantity) {
        // 悲观锁:查询时锁定记录
        Product product = productRepository.findByIdWithLock(productId);
        
        if (product.getStock() >= quantity) {
            product.setStock(product.getStock() - quantity);
            productRepository.save(product);
            return true;
        }
        return false;
    }
}

乐观锁实现:

java 复制代码
@Service
public class InventoryServiceOptimistic {
    
    public boolean deductStock(Long productId, Integer quantity) {
        int retryCount = 0;
        final int MAX_RETRY = 3;
        
        while (retryCount < MAX_RETRY) {
            Product product = productRepository.findById(productId);
            
            if (product.getStock() < quantity) {
                return false;
            }
            
            int updated = productRepository.updateStockWithVersion(
                productId, quantity, product.getVersion());
                
            if (updated > 0) {
                return true; // 更新成功
            }
            
            retryCount++;
            // 可选:加入指数退避
            try { Thread.sleep(10 * retryCount); } catch (InterruptedException e) { break; }
        }
        return false; // 重试多次后失败
    }
}

7. 总结对比表格

特性 悲观锁 乐观锁
核心思想 先加锁,后操作 先操作,后检查
实现方式 synchronizedReentrantLockSELECT FOR UPDATE CAS、版本号、时间戳
性能 低并发下较好,高并发下差 低冲突下极好,高冲突下差
数据一致性 强一致性 最终一致性
死锁 可能发生 不会发生
适用场景 写多读少、高冲突、长事务 读多写少、低冲突、短事务
开发复杂度 简单 相对复杂
扩展性 较差 较好

8. 选择建议

  1. 默认选择乐观锁:在大多数现代应用中,读操作远多于写操作,乐观锁是更好的选择
  2. 冲突频繁用悲观锁:当业务逻辑注定会产生大量冲突时,使用悲观锁
  3. 考虑业务特性:根据数据的重要性和一致性要求做出选择
  4. 可以混合使用:在同一系统中,不同模块可以根据特性使用不同的锁策略

现代趋势:随着分布式系统和微服务的普及,基于版本的乐观锁机制越来越受欢迎,因为它更适合分布式环境,能提供更好的扩展性和性能。







相关推荐
kfepiza2 小时前
Java的`volatile`关键字 笔记251007
java
风雨同舟的代码笔记2 小时前
JDK1.8 String类源码学习
java
苹果醋32 小时前
数据结构其一 线性表
java·运维·spring boot·mysql·nginx
华仔啊2 小时前
前后端防重复提交的 6 种落地实现:从按钮禁用到 AOP 全自动防护
java·后端
墨染点香3 小时前
LeetCode 刷题【103. 二叉树的锯齿形层序遍历、104. 二叉树的最大深度、105. 从前序与中序遍历序列构造二叉树】
算法·leetcode·职场和发展
lllsure3 小时前
Java Stream API
java·开发语言
chirrupy_hamal3 小时前
IO 流篇
java
啊我不会诶3 小时前
23ICPC澳门站补题
算法·深度优先·图论
Le1Yu3 小时前
2025-10-6学习笔记
java·笔记·学习