流水潺潺可穿石 發表於 2026-1-12 09:24:30

Java CAS原理和用法总结

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、CAS 原理</li><ul class="second_class_ul"><li>1. 核心思想</li><li>2. 操作模型</li><li>3. 工作流程</li><li>4. 优点与缺点</li></ul><li>二、Java 中的 CAS 用法</li><ul class="second_class_ul"><li>1. 主要的原子类</li><li>2. 核心方法</li><li>3. 代码示例</li></ul><li>总结</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>一、CAS 原理</h2>
<p class="maodian"></p><h3>1. 核心思想</h3>
<p>CAS 是一种<strong>无锁</strong>的原子操作机制。它的核心思想是:<strong>我认为值应该是A,如果是,那我就把它改成B;如果不是A(说明被别人改过了),那我就不修改,然后可以选择重试或放弃。</strong></p>
<p>这个操作是作为一条<strong>CPU硬件指令</strong>实现的(在x86架构上是 <code>CMPXCHG</code> 指令),因此它能保证原子性,不会被线程调度打断。</p>
<p class="maodian"></p><h3>2. 操作模型</h3>
<p>CAS 操作涉及三个操作数:</p>
<ul><li><strong>内存位置(V)</strong></li><li><strong>预期的原值(A)</strong></li><li><strong>新值(B)</strong></li></ul>
<p>伪代码逻辑如下:</p>
<div class="jb51code"><pre class="brush:java;">if (V == A) {
V = B;
return true;
} else {
return false;
}</pre></div>
<p>但关键是,<strong>整个比较和交换的过程是一个不可分割的原子操作</strong>。</p>
<p class="maodian"></p><h3>3. 工作流程</h3>
<p>当一个线程想要更新一个变量时,它会:</p>
<ol><li>获取当前内存中的值,作为期望值 <code>A</code>。</li><li>计算出新值 <code>B</code>。</li><li>执行 CAS 指令,判断当前内存中的值是否还是 <code>A</code>。<ul><li>如果是,说明没有其他线程修改过,成功将值更新为 <code>B</code>。</li><li>如果不是,说明值已被其他线程修改,本次更新失败。线程通常会<strong>重试</strong>整个操作(获取新的当前值,计算新值,再次执行CAS),直到成功为止。这种重试行为就是常见的<strong>自旋</strong>。</li></ul></li></ol>
<p class="maodian"></p><h3>4. 优点与缺点</h3>
<ul><li><strong>优点</strong>:
<ul><li><strong>高性能</strong>:避免了重量级锁(如 <code>synchronized</code>)带来的线程阻塞、唤醒和上下文切换的开销,在竞争不激烈的场景下性能极高。</li><li><strong>避免死锁</strong>:由于是无锁操作,从根本上避免了死锁问题。</li></ul></li><li><strong>缺点</strong>:<ul><li><strong>ABA 问题</strong>:CAS 只检查值是否变化,但如果一个值从 A 变成 B,又被改回 A,CAS 会误以为它没变。解决方案是使用<strong>版本号</strong>或<strong>标记</strong>(如 <code>AtomicStampedReference</code>)。</li><li><strong>自旋开销</strong>:在高竞争环境下,如果线程一直失败重试,会长时间占用 CPU,消耗资源。</li><li><strong>只能保证一个共享变量的原子操作</strong>:对于多个共享变量,CAS 无法保证原子性。但可以将它们合并成一个对象,使用 <code>AtomicReference</code> 来保证原子性。</li></ul></li></ul>
<p class="maodian"></p><h2>二、Java 中的 CAS 用法</h2>
<p>在 Java 中,你不能直接使用 CPU 指令。CAS 的能力是通过 <code>sun.misc.Unsafe</code> 类中的本地(Native)方法提供的。但通常,我们不会直接使用 <code>Unsafe</code>,而是使用 JD 在 <code>java.util.concurrent.atomic</code> 包下为我们封装好的<strong>原子类</strong>。</p>
<p class="maodian"></p><h3>1. 主要的原子类</h3>
<ul><li><strong>基本类型</strong>:
<ul><li><code>AtomicInteger</code>:整型原子类</li><li><code>AtomicLong</code>:长整型原子类</li><li><code>AtomicBoolean</code>:布尔型原子类</li></ul></li><li><strong>数组类型</strong>:<ul><li><code>AtomicIntegerArray</code>:整型数组原子类</li><li><code>AtomicLongArray</code>:长整型数组原子类</li><li><code>AtomicReferenceArray</code>:引用类型数组原子类</li></ul></li><li><strong>引用类型</strong>:<ul><li><code>AtomicReference</code>:引用类型原子类</li><li><code>AtomicMarkableReference</code>:带标记位的引用类型原子类(解决ABA问题的一种方式)</li><li><code>AtomicStampedReference</code>:<strong>带版本号的引用类型原子类(解决ABA问题的标准方案)</strong></li></ul></li><li><strong>字段更新器</strong>:<ul><li><code>AtomicIntegerFieldUpdater</code>:基于反射,原子性地更新某个类的 volatile int 字段。</li><li><code>AtomicLongFieldUpdater</code></li><li><code>AtomicReferenceFieldUpdater</code></li></ul></li></ul>
<p class="maodian"></p><h3>2. 核心方法</h3>
<p>所有原子类都提供了基于 CAS 的核心方法:</p>
<ul><li><code>boolean compareAndSet(int expect, int update)</code>
<ul><li>这是最核心的方法!如果当前值等于期望值 <code>expect</code>,则原子地将值设置为 <code>update</code>,成功返回 <code>true</code>,失败返回 <code>false</code>。</li></ul></li><li><code>int getAndSet(int newValue)</code><ul><li>原子地设置为新值,并返回旧值。底层通常通过循环 CAS 实现。</li></ul></li><li><code>int getAndIncrement()</code> / <code>int getAndDecrement()</code><ul><li>原子地递增/递减 1,返回旧值。<code>i++</code> 的原子版本。</li></ul></li><li><code>int getAndAdd(int delta)</code><ul><li>原子地加上 delta,返回旧值。</li></ul></li><li><code>int incrementAndGet()</code> / <code>int decrementAndGet()</code><ul><li>原子地递增/递减 1,返回新值。<code>++i</code> 的原子版本。</li></ul></li></ul>
<p class="maodian"></p><h3>3. 代码示例</h3>
<p><strong>示例 1:使用 AtomicInteger 实现线程安全的计数器</strong></p>
<div class="jb51code"><pre class="brush:java;">import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
    public static void main(String[] args) throws InterruptedException {
      AtomicInteger atomicInt = new AtomicInteger(0);
      Runnable task = () -&gt; {
            for (int i = 0; i &lt; 1000; i++) {
                // 内部通过循环CAS操作实现原子递增
                atomicInt.incrementAndGet();
            }
      };
      Thread thread1 = new Thread(task);
      Thread thread2 = new Thread(task);
      thread1.start();
      thread2.start();
      thread1.join();
      thread2.join();
      // 结果总是 2000,保证了原子性
      System.out.println("Final Count: " + atomicInt.get());
    }
}</pre></div>
<p><strong>示例 2:手动使用 compareAndSet 进行自旋</strong></p>
<div class="jb51code"><pre class="brush:java;">AtomicInteger atomicInt = new AtomicInteger(0);
int oldValue, newValue;
do {
    oldValue = atomicInt.get(); // 获取当前值作为预期值
    newValue = oldValue + 1;    // 计算新值
} while (!atomicInt.compareAndSet(oldValue, newValue));
// 如果CAS失败(oldValue已不是当前值),则循环重试</pre></div>
<p><strong>示例 3:使用 AtomicStampedReference 解决 ABA 问题</strong></p>
<div class="jb51code"><pre class="brush:java;">import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
    // 初始值为 100,版本号(Stamp)为 0
    static AtomicStampedReference&lt;Integer&gt; atomicStampedRef =
            new AtomicStampedReference&lt;&gt;(100, 0);
    public static void main(String[] args) throws InterruptedException {
      int initialStamp = atomicStampedRef.getStamp(); // 获取初始版本号
      // 线程1模拟ABA操作
      Thread thread1 = new Thread(() -&gt; {
            // 先改成 101,版本号+1
            atomicStampedRef.compareAndSet(100, 101, initialStamp, initialStamp + 1);
            // 再改回 100,版本号再+1
            atomicStampedRef.compareAndSet(101, 100, initialStamp + 1, initialStamp + 2);
      });
      // 线程2尝试修改
      Thread thread2 = new Thread(() -&gt; {
            // 先睡一会儿,确保线程1完成了ABA操作
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            // 尝试修改。虽然期望值还是100,但版本号已经从0变成了2,所以CAS会失败!
            boolean success = atomicStampedRef.compareAndSet(
                  100,
                  202,
                  initialStamp, // 传入旧的版本号0
                  initialStamp + 1
            );
            System.out.println("CAS successful? " + success); // 输出:false
      });
      thread1.start();
      thread2.start();
      thread1.join();
      thread2.join();
    }
}</pre></div>
<p class="maodian"></p><h2>总结</h2>
<table><thead><tr><th>特性</th><th>描述</th></tr></thead><tbody><tr><td><strong>本质</strong></td><td>一条<strong>CPU原子指令</strong>,通过 <code>Unsafe</code> 类提供给 Java 开发者使用。</td></tr><tr><td><strong>实现</strong></td><td>JDK 的 <code>java.util.concurrent.atomic</code> 包下的原子类对其进行了封装。</td></tr><tr><td><strong>核心方法</strong></td><td><code>compareAndSet(expectedValue, newValue)</code></td></tr><tr><td><strong>优点</strong></td><td><strong>无锁</strong>、<strong>高性能</strong>(低竞争时)、<strong>避免死锁</strong>。</td></tr><tr><td><strong>缺点</strong></td><td><strong>ABA问题</strong>(用版本号解决)、<strong>自旋CPU开销</strong>(高竞争时)。</td></tr><tr><td><strong>应用场景</strong></td><td>计数器、序列号生成器、<code>ConcurrentHashMap</code> 等高性能并发容器的实现。</td></tr></tbody></table>
<p>CAS 是现代并发包(JUC)的基石,理解了它就能更好地理解 <code>ReentrantLock</code>、线程池等高级并发工具的内部工作原理。</p>
<p>到此这篇关于java CAS原理和用法的文章就介绍到这了,更多相关java CAS原理内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Java多线程并发Unsafe与CAS的用法解读</li><li>Java switch case语句的用法及常见问题</li><li>Java中的CAS无锁机制实现原理详解</li><li>详解Java中CAS机制的原理与优缺点</li><li>Java并发之CAS原理详解</li><li>Java CAS基本实现原理代码实例解析</li><li>Java原子操作CAS原理解析</li><li>Java并发的CAS原理与ABA问题的讲解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Java CAS原理和用法总结