原子变量和CAS算法

  先运行下面一段程序:

package concurrent;

class AtomicDemo implements Runnable {

    private int serialNumber = 0;

    public int getSerialNumber() {
        return serialNumber++;
    }

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(getSerialNumber());
    }

}

public class TestAtomic {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();

        for(int i=0;i<10;i++)
            new Thread(ad).start();

    }

}

  运行程序,程序并不是总能分别输出0-9这10个数字:
  
  image_1b8hndnts1d6t14et14p61hjkr619.png-13.9kB
  
  image_1b8hneg4i16eb1nhr40ns86e81m.png-20.7kB
  
  这是因为,java中的自增操作并不是原子操作。i++ 的操作实际上分为三个步骤“读-改-写”,例如下面这道面试题:

   int i = 10;
  int a  = i++; //10

  实际上是分为下面的步骤(仅模拟):

  int temp = i;
  i = i + 1;
  a = temp;

  而在本例中,由于没有将自增后的值赋给其他变量,实际上是分为了下面两个步骤:

  int temp = serialNumber; // 第一步
  serialNumber = serialNumber + 1; //第二步

  所以出现上述情况的原因是,当某一个线程A执行完第一步但还没有执行第二步时,读取到某个值比如是5,这时候另一个线程B执行完了第一步和第一步,即读取到了值5,并且将其修改为6然后输出,然后线程A继续执行第二步,也将值5自增得到的6输出了,所以结果中就出现了两个“6”。
  
  我们尝试将serialNumber用volatile修饰,发现也不能解决问题,这是因为,volatile只能解决内存可见性的问题,并不能解决原子性的问题。要解决这个问题,JUC为我们提供了一些原子变量,放在java.util.concurrent.atomic包下:
  
  image_1b8hpfpg1197inv2b3tpgnd0v13.png-51kB
  
  例如,我们现在使用AtomicInteger代替int类型:

package concurrent;

import java.util.concurrent.atomic.AtomicInteger;

class AtomicDemo implements Runnable {

    private AtomicInteger serialNumber = new AtomicInteger(0);

    public int getSerialNumber() {
        return serialNumber.getAndIncrement();
    }

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(getSerialNumber());
    }

}

public class TestAtomic {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();

        for(int i=0;i<10;i++)
            new Thread(ad).start();

    }

}

  运行程序,不会再输出重复的元素:
  
  image_1b8hphrtq1pst1gta59i1vag1521g.png-21.3kB
  
  那么,这些原子变量类型是如何实现同步效果的呢?
  首先,原子变量类型中的所有域都使用了volatile关键字修饰以保证内存可见性;然后,使用了CAS(Compare and Swap)算法保证数据变量的原子性。
  
CAS算法
  CAS算法是硬件对于并发操作的支持,其中包含了三个操作数:内存值,预估值和更新值。每当要执行更新操作时,会先在同步方法中比较内存值和预估值是否相等,如果相等才会用更新值替换内存值,否则什么也不做。
  下面我们可以用一段程序模拟一下CAS算法:

package concurrent;

/*
 * 模拟CAS算法
 */

class CAS {
    // 内存值
    private volatile int value;

    // 获取内存值
    public synchronized int getValue() {
        return value;
    }

    //无论更新成功还是失败,都会返回旧的内存值
    public synchronized int compareAndSwap(int expectedValue, int newValue) {
        int oldValue = value;

        if (oldValue == expectedValue) {
            this.value = newValue;
        }

        return oldValue;
    }

    //判断更新是否成功,如果更新成功,旧的内存值会和预估值相等
    public synchronized boolean compareAndSet(int expectedValue, int newValue) {
        return expectedValue == compareAndSwap(expectedValue, newValue);
    }
}

public class TestCAS {

    public static void main(String[] args) {

        CAS cas = new CAS();

        //创建10个线程来模拟多线程环境
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    int expectedValue = cas.getValue();
                    boolean b = cas.compareAndSet(expectedValue, (int) (Math.random() * 101));
                    System.out.println(b);
                }
            }).start();
        }
    }
}

  运行程序,发现输出结果既有成功,也有失败,这是符合逻辑的:
  
  image_1b8hs34hr6eo1c3o1s8p1pdohr41t.png-22.2kB

<think>嗯,用户想了解CAS算法的工作原理实现代码。首先,我需要回忆一下CAS的基本概念。CAS全称是Compare And Swap,是一种无锁的并发控制机制,用于实现多线程环境下的原子操作。它的核心思想是,先比较内存中的值是否与预期值相同,如果相同,则更新为新值,否则不进行操作。这个过程是原子的,可以避免使用传统的锁机制带来的性能开销。 接下来,我需要确定用户可能需要的细节。用户提到了“详解”“实现代码示例”,所以应该从原理、实现步骤、代码示例、优缺点等方面展开。需要结合提供的引用内容,特别是引用[1]、[2]、[3]中的代码示例,可能还需要解释ABA问题解决办法。 首先,CAS的工作原理。需要解释CAS的三个操作数:内存地址V、旧的预期值A、新值B。当且仅当V的值等于A时,CAS才会将V的值更新为B,否则不执行任何操作。整个过程是一个原子操作,由CPU指令支持,比如在x86架构中的CMPXCHG指令。 然后,实现代码部分。用户提供的引用中有Java的AtomicInteger类的例子,比如compareAndSet方法。引用[3]中的模拟CAS代码也很有参考价值,虽然是用Java实现的,但展示了如何通过同步方法模拟CAS原子性。需要解释这些代码的结构,比如CompareAndSwap类中的方法如何工作,以及如何在多线程环境中使用。 此外,需要注意ABA问题。用户可能不太了解这个问题,所以需要解释ABA现象及其解决方案,比如使用版本号或时间戳,引用[2]中提到AtomicStampedReference可以解决ABA问题。这部分可能需要一个简单的例子说明问题及解决办法。 然后,代码示例部分。需要给出具体的Java代码,展示如何使用AtomicInteger的CAS方法,以及如何自定义一个模拟CAS的类。比如引用[1]中的示例代码,展示compareAndSet的使用;引用[3]中的CompareAndSwap类,说明如何通过synchronized方法实现类似CAS的逻辑。 最后,总结CAS的应用场景优缺点。优点包括减少线程阻塞,提升并发性能;缺点包括ABA问题、自旋带来的CPU消耗,以及只能保证单个变量原子性。需要结合引用[4]提到的ForkJoinPool,说明CAS在并发框架中的应用。 在整理回答时,要注意结构清晰,分步骤讲解,确保用户能逐步理解。同时,检查LaTeX格式是否正确,行内公式用$...$,独立公式用$$...$$,并生成相关问题。例如,用户可能想知道CASJava中的具体应用,或者如何避免ABA问题,所以相关问题可以围绕这些点展开。</think>### CAS算法详解 #### 一、工作原理 CAS(Compare And Swap)是一种无锁并发控制机制,核心思想通过**原子性比较与交换**实现线程安全。其操作逻辑为: 1. 比较内存位置$V$的当前值是否等于预期值$A$ 2. 若相等则将内存值更新为新值$B$ 3. 无论是否更新都返回原始值 数学表达式为: $$ \text{CAS}(V, A, B) = \begin{cases} \text{true} & \text{if } V = A \text{ then } V \gets B \\ \text{false} & \text{otherwise} \end{cases} $$ 该过程由CPU的原子指令(如x86的`CMPXCHG`)直接支持[^1]。 #### 二、Java实现示例 ```java // 引用[2]中的CAS实现示例 public class CASAlgorithm { AtomicInteger num = new AtomicInteger(0); void update() { int oldValue = num.get(); while(true) { if(num.compareAndSet(oldValue, oldValue + 1)) { return; } oldValue = num.get(); // 更新旧值继续尝试 } } } ``` **代码解析**: - `AtomicInteger.compareAndSet()`实现原子操作 - **自旋锁**机制保证最终成功 - 通过循环重试解决多线程竞争问题[^2] #### 三、ABA问题与解决方案 **ABA问题场景**: 1. 线程1读取值$A$ 2. 线程2将$A→B→A$ 3. 线程1的CAS仍会成功,导致逻辑错误 **解决方案**: 使用带版本号的`AtomicStampedReference`: ```java AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0); ref.compareAndSet(100, 200, 0, 1); // 对比值版本号 ``` #### 四、算法特性对比 | 特性 | 锁机制 | CAS算法 | |-----------|-------------|-------------| | 线程阻塞 | 是 | 否 | | 上下文切换开销 | 高 | 无 | | 实现复杂度 | 低 | 高 | | 适用场景 | 临界区复杂操作 | 简单原子操作 | #### 五、应用场景 1. 计数器累加(如访问量统计) 2. 无锁队列实现 3. 并发容器(如ConcurrentHashMap的sizeCtl) 4. ForkJoinPool中的任务调度[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值