2026/3/27 9:15:51
网站建设
项目流程
深圳深一互联科技有限公司,鹤壁做网站优化,内蒙网络_网站建设,新桥做网站公司一、前言 在 Java 并发编程的世界里#xff0c;我们总是在和 “线程安全”“性能优化” 打交道。传统的锁机制虽然能解决线程安全问题#xff0c;但也带来了不少性能损耗。而今天要介绍的 Compare and Swap#xff08;简称 CAS#xff09;#xff0c;作为无锁编程的核心思…一、前言在 Java 并发编程的世界里我们总是在和 “线程安全”“性能优化” 打交道。传统的锁机制虽然能解决线程安全问题但也带来了不少性能损耗。而今天要介绍的 Compare and Swap简称 CAS作为无锁编程的核心思想正在为高并发场景提供更高效的解决方案。本文将从锁的困境出发带你全面认识 CAS 的本质、原理、特性并通过实战代码验证其价值。二、并发编程中的“锁”困境在接触 CAS 之前我们先思考一个经典问题如何实现一个线程安全的计数器很多开发者首先会想到使用 synchronized 关键字它就像一把 “排他锁”能保证同一时间只有一个线程执行临界区代码。比如下面这段简单的实现/** * 基于synchronized的线程安全计数器 */ public class SynchronizedCounter { private int count 0; // 加锁保证计数操作的原子性 public synchronized void increment() { count; } public int getCount() { return count; } }这段代码确实能保证线程安全但 synchronized 尤其是早期 JDK 中的重量级锁存在明显的问题1. 传统 synchronized 锁的核心问题性能开销大当线程获取锁失败时会被阻塞并进入等待状态这涉及到操作系统的线程上下文切换。上下文切换需要保存当前线程的执行状态、恢复目标线程的执行状态这个过程会消耗大量的 CPU 资源在高并发场景下频繁的上下文切换会严重拖累系统性能。排他性导致并发效率低synchronized 是排他锁即使多个线程只是读取共享变量也会被串行化执行JDK1.6 后虽有优化但本质仍是排他性锁的核心逻辑。这种串行化执行无法充分利用多核 CPU 的并行处理能力在高并发读写场景下性能瓶颈会非常明显。2. 无锁编程的优势为了解决锁机制的痛点无锁编程应运而生。它的核心优势在于无需加锁和解锁操作减少了锁相关的性能开销线程不会被阻塞避免了上下文切换的损耗能充分利用多核 CPU 的并行能力提升高并发场景下的处理效率。而 CAS正是无锁编程的 “基石”几乎所有的 Java 无锁并发工具都是基于 CAS 思想实现的。三、CAS 的核心定义与工作原理CAS即比较并交换是一种基于硬件实现的原子操作它能在无锁的情况下保证共享变量操作的原子性。1. CAS 三要素要理解 CAS 的工作原理首先要明确它的三个核心参数缺一不可内存地址 V表示要操作的共享变量在内存中的存储地址可以简单理解为这个共享变量本身预期原值 A表示线程在操作共享变量前读取到的该变量的预期值新值 B表示线程想要将共享变量更新后的目标值。2. CAS 核心逻辑CAS 的执行逻辑可以用一句话概括仅当内存地址 V 对应的实际值与线程持有的预期原值 A 完全相等时才将 V 的值原子性地更新为新值 B如果不相等则说明该共享变量已经被其他线程修改过当前线程不执行任何更新操作直接返回失败。整个 CAS 操作是一个原子性的操作由硬件层面保证其不可分割也就是说在 CAS 操作执行期间不会被其他线程打断这是它能保证线程安全的核心原因。3. 通俗举例用生活场景类比 CAS为了让大家更好地理解我们用一个生活中常见的 “多人抢限量商品” 场景来类比 CAS假设某电商平台有 1 件限量商品对应内存地址 V你在下单前看到商品库存是 1对应预期原值 A你准备下单购买此时你期望将库存更新为 0对应新值 B。CAS 成功场景当你发起下单请求时系统检查商品库存V确实是 1和你的预期 A 一致于是立即将库存更新为 0新值 B你成功抢到商品CAS 操作执行成功CAS 失败场景在你发起下单请求的瞬间另一个用户已经先你一步完成下单商品库存V已经被更新为 0和你的预期 A1 不一致系统不会执行任何更新操作你收到 “商品已售罄” 的提示CAS 操作执行失败。这个过程中系统没有给商品加 “锁”不需要等待其他用户操作完成而是通过 “比较 - 交换” 的原子操作实现了库存更新的线程安全这就是 CAS 的核心思想。四、CAS 的核心特性CAS 能成为无锁编程的核心得益于它的三个关键特性这些特性也是它与传统锁机制的核心区别1. 原子性这是 CAS 的核心特性由 CPU 的原子指令直接支撑后续文章会详细讲解。CAS 操作的 “比较” 和 “交换” 是一个不可分割的整体在操作完成前不会被任何其他线程中断这保证了共享变量操作的线程安全无需额外的锁机制保护。2. 无锁性CAS 操作全程不需要获取和释放锁线程在执行 CAS 操作时不会因为竞争资源而被阻塞。即使 CAS 操作失败线程也可以选择重试或放弃操作整个过程没有锁的上下文切换开销这是 CAS 性能优于传统锁的关键原因。3. 乐观性CAS 是一种 “乐观锁” 思想的实现注意乐观锁并非实际意义上的锁而是一种并发控制策略。它的核心假设是大多数情况下共享变量的并发冲突是很少发生的。基于这个假设CAS 不会预先阻塞线程而是直接尝试更新共享变量。只有当检测到共享变量被其他线程修改比较失败时才会处理冲突重试或放弃。这种 “先尝试失败再处理” 的策略在低冲突场景下性能远高于传统的 “先加锁再执行” 的悲观锁策略。五、入门实践Java JDK 的 java.util.concurrent.atomic 包下提供了一系列基于 CAS 实现的原子类 AtomicInteger 就是其中最常用的一个它能保证整数类型变量的原子性操作。下面我们通过实战代码对比 synchronized 和 AtomicInteger 的实现直观感受 CAS 的优势。1. AtomicInteger 的核心 CAS 方法AtomicInteger 提供了 compareAndSet(int expect, int update) 方法这是它的核心 CAS 方法方法含义如下expect预期的共享变量原值对应 CAS 三要素中的 Aupdate想要更新的新值对应 CAS 三要素中的 B返回值boolean 类型 true 表示 CAS 更新成功 false 表示更新失败。此外 AtomicInteger 还提供了 incrementAndGet() 原子自增并返回新值、 getAndIncrement() 原子自增并返回旧值等常用方法这些方法底层都是基于 CAS 实现的。2. 代码示例实现线程安全的计数器我们分别用 synchronized 和 AtomicInteger 实现计数器并在高并发场景下测试它们的性能和线程安全性。示例 1基于 synchronized 的计数器对照组import java.util.concurrent.CountDownLatch; /** * 基于synchronized的线程安全计数器对照组 */ public class SynchronizedCounterTest { // 计数器初始值 private static SynchronizedCounter counter new SynchronizedCounter(); // 线程数模拟高并发场景 private static final int THREAD_NUM 1000; // 每个线程执行的计数次数 private static final int INCR_NUM 1000; // 倒计时门栓用于等待所有线程执行完成 private static CountDownLatch countDownLatch new CountDownLatch(THREAD_NUM); public static void main(String[] args) throws InterruptedException { // 记录开始时间 long startTime System.currentTimeMillis(); // 创建并启动线程 for (int i 0; i THREAD_NUM; i) { new Thread(() - { try { // 每个线程执行1000次自增操作 for (int j 0; j INCR_NUM; j) { counter.increment(); } } finally { // 线程执行完成门栓计数减1 countDownLatch.countDown(); } }).start(); } // 等待所有线程执行完成 countDownLatch.await(); // 记录结束时间 long endTime System.currentTimeMillis(); // 输出结果 System.out.println(基于synchronized的计数器最终值 counter.getCount()); System.out.println(执行耗时 (endTime - startTime) 毫秒); } /** * 基于synchronized的计数器实现 */ static class SynchronizedCounter { private int count 0; public synchronized void increment() { count; } public int getCount() { return count; } } }示例 2基于 AtomicInteger 的计数器试验组CAS 实现import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; /** * 基于AtomicInteger的线程安全计数器试验组CAS实现 */ public class AtomicIntegerCounterTest { // 计数器初始值基于AtomicInteger实现 private static AtomicInteger atomicInteger new AtomicInteger(0); // 线程数与对照组保持一致 private static final int THREAD_NUM 1000; // 每个线程执行的计数次数与对照组保持一致 private static final int INCR_NUM 1000; // 倒计时门栓与对照组保持一致 private static CountDownLatch countDownLatch new CountDownLatch(THREAD_NUM); public static void main(String[] args) throws InterruptedException { // 记录开始时间 long startTime System.currentTimeMillis(); // 创建并启动线程 for (int i 0; i THREAD_NUM; i) { new Thread(() - { try { // 每个线程执行1000次原子自增操作 for (int j 0; j INCR_NUM; j) { // incrementAndGet()原子自增底层基于CAS实现 atomicInteger.incrementAndGet(); } } finally { // 线程执行完成门栓计数减1 countDownLatch.countDown(); } }).start(); } // 等待所有线程执行完成 countDownLatch.await(); // 记录结束时间 long endTime System.currentTimeMillis(); // 输出结果 System.out.println(基于AtomicInteger的计数器最终值 atomicInteger.get()); System.out.println(执行耗时 (endTime - startTime) 毫秒); } }3. 运行结果分析我们分别运行上述两段代码得到的典型结果如下不同机器配置下耗时略有差异但趋势一致运行结果 1基于 synchronized 的计数器基于synchronized的计数器最终值1000000 执行耗时85 毫秒运行结果 2基于 AtomicInteger 的计数器基于AtomicInteger的计数器最终值1000000 执行耗时12 毫秒从结果中我们可以得出两个关键结论线程安全性验证两段代码的计数器最终值都等于 1000*10001000000 说明 synchronized 和 AtomicInteger 都能保证线程安全避免了并发场景下计数丢失的问题性能优势验证基于 CAS 实现的 AtomicInteger 执行耗时远低于基于 synchronized 的计数器这充分体现了 CAS 无锁编程在高并发场景下的性能优势。此外我们还可以直接使用 AtomicInteger 的 compareAndSet 方法手动实现 CAS 操作代码示例如下import java.util.concurrent.atomic.AtomicInteger; /** * 手动使用AtomicInteger的compareAndSet方法实现CAS操作 */ public class CASManualDemo { public static void main(String[] args) { AtomicInteger atomicInteger new AtomicInteger(10); // 第一次CAS操作预期值10新值20 boolean firstCas atomicInteger.compareAndSet(10, 20); System.out.println(第一次CAS操作是否成功 firstCas); System.out.println(CAS操作后的值 atomicInteger.get()); // 第二次CAS操作预期值10与当前实际值20不一致新值30 boolean secondCas atomicInteger.compareAndSet(10, 30); System.out.println(第二次CAS操作是否成功 secondCas); System.out.println(CAS操作后的值 atomicInteger.get()); } }运行结果第一次CAS操作是否成功true CAS操作后的值20 第二次CAS操作是否成功false CAS操作后的值20这个示例直观地验证了 CAS 的核心逻辑只有预期值与实际值一致时才能完成更新操作。六、总结CAS 作为无锁编程的核心思想以 “原子性、无锁性、乐观性” 为核心特性解决了传统锁机制的性能损耗与并发效率问题是 Java 高并发场景下实现线程安全与性能优化的关键技术而atomic包下的原子类如AtomicInteger则是 CAS 思想在 Java 中的便捷落地载体在高并发计数器等场景中具有显著的性能优势。