JAVA模拟CAS

本文通过对比不安全的计数器与使用CAS机制的计数器,介绍了比较并交换(CAS)原理及其在Java中的应用。通过具体代码示例展示了如何利用CAS解决线程安全问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        先来看一个线程不安全的Counter:
package org.wangld.test.concurrence.cas;

public class UnsafeCounter implements Runnable {
    
    private int counter = 0;

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(counter++);
        }
    }
    
    public static void main(String[] args) {
        Runnable run = new UnsafeCounter();

        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}
        因为counter++是一个非原子性操作,可以分成读counter,加1,写counter这3步,这就导致了线程安全问题。这个例子中,每个线程要让counter加10000次1,10个线程就是100000,但是最终的执行结果基本都不到100000.
        下面来看使用JAVA5以及其后版本提供的原子变量实现的Counter:
package org.wangld.test.concurrence.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter implements Runnable {
    
    private AtomicInteger value = new AtomicInteger();
    
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(value.incrementAndGet());
        }
    }
    
    //安全
    public static void main(String[] args) {
        Runnable run = new AtomicCounter();

        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}
        上面这个计数器没有使用锁,但在面对并发的时候就不存在线程安全问题。因为AtomicInteger采用了系统的CAS。下面这段话讲解了CAS,摘自《JAVA并发编程实践》:
        大多数现代处理器都包含对多处理的支持。当然这种支持包括多处理器可以共享外部设备和主内存,同时它通常还包括对指令系统的增加来支持多处理的特殊要求。特别是,几乎每个现代处理器都有通过可以检测或阻止其他处理器的并发访问的方式来更新共享变量的指令。
        现在的处理器(包括 Intel 和 Sparc 处理器)使用的最通用的方法是实现名为“比较并交换(Compare And Swap)”或 CAS 的原语。(在 Intel 处理器中,比较并交换通过cmpxchg 系列指令实现。PowerPC 处理器有一对名为“加载并保留”和“条件存储”的指令,它们实现相同的目地;MIPS 与 PowerPC 处理器相似,除了第一个指令称为“加载链接”。)
        CAS 操作包含三个操作数—— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
        通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
        类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。
        现在我们模拟CAS来实现一个计数器:
package org.wangld.test.concurrence.cas;

public class CASCounter implements Runnable {
    
    private SimilatedCAS counter = new SimilatedCAS();

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(this.increment());
        }
    }
    
    public int increment() {
        int oldValue = counter.getValue();
        int newValue = oldValue + 1;
        while (!counter.compareAndSwap(oldValue, newValue)) {//如果CAS失败,就去拿新值继续执行CAS
            oldValue = counter.getValue();
            newValue = oldValue + 1;
        }
        
        return newValue;
    }
    
    public static void main(String[] args) {
        Runnable run = new CASCounter();

        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}

class SimilatedCAS {
    
    private int value;
    
    public int getValue() {
        return value;
    }
    
    // 这里只能用synchronized了,毕竟无法调用操作系统的CAS
    public synchronized boolean compareAndSwap(int expectedValue, int newValue) {
        if (value == expectedValue) {
            value = newValue;
            return true;
        }
        
        return false;
    }
}
        如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作,就可以说该算法是“无等待”的。“无锁定算法”要求某个线程总是执行操作(无等待的另一种定义是保证每个线程在其有限的步骤中正确计算自己的操作,而不管其他线程的操作、计时、交叉或速度)。基于 CAS 的并发算法称为“无锁定算法”,因为线程不必再等待锁定(有时称为互斥或关键部分,这取决于线程平台的术语)。无论 CAS 操作成功还是失败,在任何一种情况中,它都在可预知的时间内完成。如果 CAS 失败,调用者可以重试 CAS 操作或采取其他适合的操作。
<think>嗯,用户问的是“java案例cas”,我需要提供一个Java中使用CAS(Compare-And-Swap)的具体例子。之前用户已经询问过CAS的概述,现在需要实际案例。首先,我应该回顾一下CAS的基本概念,确保案例能准确体现CAS的应用。 用户可能是在学习并发编程,或者在开发中遇到了线程安全的问题,想通过实际例子理解如何用CAS解决。他们可能对AtomicInteger或其他原子类的内部机制感兴趣,或者想自己实现一个基于CAS线程安全类。 我应该选择一个常见的场景,比如计数器,因为这是CAS的经典应用。AtomicInteger的例子很直接,但用户可能希望看到更详细的代码示例,包括compareAndSet的使用。同时,可能需要解释ABA问题,因为这是CAS的一个常见问题,但用户可能还不了解。 另外,用户可能想知道CAS和synchronized的区别,或者为什么在某些情况下选择CAS而不是锁。在案例中,我应该强调CAS的无锁特性和性能优势,特别是在高并发环境下的效率提升。 需要确保代码示例简洁明了,有注释说明每个步骤的作用。可能还需要指出潜在的问题,比如ABA问题,并给出解决方案,比如使用AtomicStampedReference。这样用户不仅能得到案例,还能了解相关的注意事项。 最后,用户可能希望知道何时使用CAS,何时使用锁,或者原子类在实际开发中的应用场景。在总结部分,应该给出一些建议,帮助用户根据具体情况选择合适的同步机制。</think>### Java CAS 典型案例:实现线程安全的计数器 通过`AtomicInteger`演示CAS(Compare-And-Swap)在多线程环境下的原子操作。 --- #### **代码示例** ```java import java.util.concurrent.atomic.AtomicInteger; public class CASCounter { // 使用 AtomicInteger 替代普通 int private AtomicInteger count = new AtomicInteger(0); // 使用 CAS 原子操作递增计数器 public void increment() { int current; int next; do { current = count.get(); // 获取当前值 next = current + 1; // 计算新值 // CAS 操作:如果当前值未被其他线程修改,则更新为新值 } while (!count.compareAndSet(current, next)); } public int getCount() { return count.get(); } // 模拟线程并发操作 public static void main(String[] args) throws InterruptedException { CASCounter counter = new CASCounter(); int threadCount = 100; // 创建多个线程并发递增计数器 Thread[] threads = new Thread[threadCount]; for (int i = 0; i < threadCount; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 1000; j++) { counter.increment(); } }); threads[i].start(); } // 等待所有线程执行完成 for (Thread thread : threads) { thread.join(); } // 最终结果应为 100,000(100线程 × 1000次) System.out.println("最终计数: " + counter.getCount()); } } ``` --- #### **核心逻辑解析** 1. **CAS操作流程** - `current = count.get()`:获取当前值。 - `next = current + 1`:计算新值。 - `compareAndSet(current, next)`:**原子性**尝试更新值,若当前值与预期值一致(未被其他线程修改),则更新成功;否则循环重试。 2. **线程安全保证** - 无需使用`synchronized`锁,通过硬件级CAS指令(如x86的`CMPXCHG`)实现原子性。 - 避免线程阻塞,提高并发性能。 3. **输出结果验证** - 100个线程各执行1000次递增,最终结果应为`100 * 1000 = 100,000`,验证CAS的正确性。 --- #### **CAS的典型应用场景** 1. **无锁数据结构** - 如`ConcurrentHashMap`的分段锁优化(JDK8+使用CAS + synchronized)。 2. **高并发计数器** - 如秒杀系统中的库存扣减、分布式ID生成器。 3. **状态标志更新** - 如线程池的状态切换(RUNNING → SHUTDOWN)。 --- #### **CAS的局限性** 1. **ABA问题** - 若值从A变为B再变回A,CAS会误判为“未修改”。 - **解决方案**:使用`AtomicStampedReference`添加版本号戳。 2. **自旋开销** - 在高竞争环境下,频繁重试可能导致CPU资源浪费。 3. **只能保证单个变量的原子性** - 复杂对象需结合`Unsafe`或`VarHandle`操作字段级原子性。 --- #### **扩展:ABA问题修复示例** ```java import java.util.concurrent.atomic.AtomicStampedReference; public class ABAFixedExample { private static final AtomicStampedReference<Integer> value = new AtomicStampedReference<>(100, 0); public static void main(String[] args) { int[] stamp = new int[1]; // 保存版本号 value.get(stamp); // 获取当前值和版本号 // 线程1尝试更新值(预期值100,版本号0) boolean success = value.compareAndSet(100, 101, stamp[0], stamp[0] + 1); System.out.println("更新结果: " + success); // 成功 // 线程2尝试更新值(预期值100,版本号0) → 因版本号已变化,更新失败 boolean failed = value.compareAndSet(100, 101, stamp[0], stamp[0] + 1); System.out.println("ABA更新失败: " + failed); // false } } ``` --- ### **总结** CAS通过硬件级原子指令实现轻量级同步,适用于**简单变量操作**的高并发场景,但需警惕ABA问题和自旋开销。在复杂逻辑或竞争激烈场景下,可结合锁或使用`AtomicStampedReference`增强安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值