并发编程之- Atomic

发布于:2025-07-18 ⋅ 阅读:(17) ⋅ 点赞:(0)

并发编程之- Atomic类原理与实践

引言:并发编程中的原子性挑战

在多线程环境下,共享变量的并发访问往往导致数据竞争(Data Race)结果不一致问题。以简单的计数器为例:

private long counter = 0;
public void increment() {
    counter++; // 非原子操作:读取-修改-写入
}

在单线程中上述代码正常工作,但在多线程环境下,counter++会被拆分为三个步骤(读取当前值→加1→写回内存),若两个线程同时执行,可能导致其中一个线程的更新被覆盖。传统解决方案如synchronizedLock通过独占锁保证原子性,但会带来上下文切换线程阻塞的性能开销。

Java并发包(java.util.concurrent.atomic)提供的Atomic类通过无锁编程(Lock-Free) 思想,基于硬件级别的CAS(Compare-And-Swap) 指令实现原子操作,在保证线程安全的同时显著提升并发性能。本文将从底层原理到实战应用,全面剖析Atomic类的设计与实践。

一、Atomic类家族概述

Atomic类并非单一实现,而是一个包含12个核心类的家族,可分为五大类,覆盖基本类型、数组、引用对象、字段更新等场景:

分类 核心类 作用
基本类型原子类 AtomicIntegerAtomicLongAtomicBoolean int/long/boolean类型变量提供原子操作
数组类型原子类 AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray 对数组元素进行原子操作,支持通过索引访问
引用类型原子类 AtomicReferenceAtomicStampedReferenceAtomicMarkableReference 对对象引用进行原子操作,解决ABA问题(后两者)
字段更新器原子类 AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater 基于反射机制,原子更新对象的volatile字段(无需修改原有类结构)
累加器原子类 LongAdderDoubleAdderLongAccumulatorDoubleAccumulator 高并发场景下的累加优化,通过分段累加减少竞争(JDK 8新增)

核心设计思想

所有Atomic类均基于CAS机制实现,其核心优势在于:

  • 细粒度并发控制:将竞争范围缩小到变量级别,优于锁的粗粒度控制;
  • 无阻塞特性:线程无需阻塞等待锁释放,失败时通过自旋重试,减少上下文切换;
  • 硬件级原子性:依赖CPU的cmpxchg指令,保证操作的原子性。

二、CAS机制:Atomic类的底层基石

2.1 CAS原理与流程

CAS(Compare-And-Swap)是一种乐观锁策略,核心思想是**“无锁更新”**:当线程要更新变量时,先比较变量当前值是否等于预期值,若相等则更新为新值,否则放弃操作并重试。其操作包含三个参数:

  • V(内存值):变量在内存中的实际值;
  • A(预期值):线程认为变量应有的值;
  • B(新值):需要写入的新值。

流程示意图(建议使用mermaid绘制):

graph TD
    A[读取内存值V] --> B{比较V == A?};
    B -- 是 --> C[将V更新为B,返回成功];
    B -- 否 --> D[放弃更新,返回失败];

2.2 CAS的实现:Unsafe类与VarHandle

Java中CAS操作通过sun.misc.Unsafe类实现,该类提供了直接操作内存的native方法。以AtomicInteger为例:

public class AtomicInteger extends Number implements Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset; // value字段的内存偏移量

    static {
        try {
            // 获取value字段相对于AtomicInteger对象的内存偏移量
            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value; // 共享变量,volatile保证可见性

    public final boolean compareAndSet(int expect, int update) {
        // 调用Unsafe的CAS方法
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

JDK 9+的变化Unsafe因安全性问题被封装,推荐使用java.lang.invoke.VarHandle替代,提供更安全的内存访问API。

2.3 CAS的局限性与解决方案

(1)ABA问题

问题描述:变量值从A被修改为B,再改回A,CAS会误认为值未变化。例如:

  • 线程1读取值A,准备更新为C;
  • 线程2将A→B,再B→A;
  • 线程1执行CAS时发现值仍为A,更新成功,但实际中间发生过变化。

解决方案

  • AtomicStampedReference:维护值+版本号,每次更新时版本号递增,CAS需同时比较值和版本号;
  • AtomicMarkableReference:维护值+布尔标记,仅标记是否被修改过(适用于无需记录修改次数的场景)。

示例代码:AtomicStampedReference解决ABA问题

public class ABADemo {
    public static void main(String[] args) {
        // 初始值100,版本号1
        AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(100, 1);
        
        // 线程1:执行ABA操作
        new Thread(() -> {
            int stamp = asr.getStamp();
            System.out.println("线程1初始版本号:" + stamp); // 1
            // A→B
            asr.compareAndSet(100, 101, stamp, stamp + 1);
            // B→A
            asr.compareAndSet(101, 100, asr.getStamp(), asr.getStamp() + 1);
            System.out.println("线程1最终版本号:" + asr.getStamp()); // 3
        }).start();

        // 线程2:尝试更新
        new Thread(() -> {
            int stamp = asr.getStamp();
            System.out.println("线程2初始版本号:" + stamp); // 1
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            // 版本号不匹配,更新失败
            boolean success = asr.compareAndSet(100, 200, stamp, stamp + 1);
            System.out.println("线程2更新结果:" + success); // false
            System.out.println("当前值:" + asr.getReference()); // 100(未被修改)
        }).start();
    }
}
(2)自旋开销

CAS失败时会通过自旋重试,高并发下可能导致大量CPU资源浪费。解决方案:

  • 自适应自旋:根据历史重试成功率动态调整自旋次数;
  • 分段累加:如LongAdder将变量拆分为多个Cell,分散竞争。
(3)只能保证单个变量原子性

CAS仅对单个变量操作有效,多变量原子性需通过或**封装对象+AtomicReference**实现。

三、核心Atomic类详解与实战

3.1 基本类型原子类:AtomicInteger/AtomicLong

适用场景:简单计数器、ID生成器等场景,提供自增、自减、累加等原子操作。

核心方法

方法名 作用 示例
incrementAndGet() 自增1,返回新值 counter.incrementAndGet()
decrementAndGet() 自减1,返回新值 counter.decrementAndGet()
addAndGet(int delta) 累加delta,返回新值 counter.addAndGet(5)
compareAndSet(int expect, int update) CAS更新,成功返回true counter.compareAndSet(3, 5)

实战案例:多线程安全计数器

public class AtomicCounter {
    private static final AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.incrementAndGet(); // 原子自增
            }
        };

        // 创建10个线程并发执行
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(task);
            threads[i].start();
        }
        for (Thread t : threads) t.join();

        System.out.println("最终计数:" + counter.get()); // 输出10000(无竞争问题)
    }
}

3.2 引用类型原子类:AtomicReference

适用场景:对对象引用进行原子更新,如原子替换缓存中的对象。

核心方法

  • compareAndSet(V expect, V update):CAS更新引用;
  • getAndSet(V newValue):设置新引用并返回旧值。

实战案例:原子更新用户信息

class User {
    private String name;
    private int age;
    // 构造器、getter、setter省略
}

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User user = new User("Alice", 20);
        AtomicReference<User> userRef = new AtomicReference<>(user);

        // 线程安全更新用户年龄
        User newUser = new User("Alice", 21);
        boolean updated = userRef.compareAndSet(user, newUser);
        System.out.println("更新结果:" + updated); // true
        System.out.println("新年龄:" + userRef.get().getAge()); // 21
    }
}

3.3 数组类型原子类:AtomicIntegerArray

适用场景:多线程操作数组元素,如并发修改像素数组、统计数组等。

核心方法

  • getAndIncrement(int i):原子自增数组第i个元素;
  • compareAndSet(int i, int expect, int update):CAS更新数组第i个元素。

实战案例:并发更新数组元素

public class AtomicArrayDemo {
    public static void main(String[] args) throws InterruptedException {
        int[] array = new int[5];
        AtomicIntegerArray atomicArray = new AtomicIntegerArray(array);

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                atomicArray.getAndIncrement(0); // 原子更新第0个元素
            }
        };

        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(task);
            threads[i].start();
        }
        for (Thread t : threads) t.join();

        System.out.println("数组[0]最终值:" + atomicArray.get(0)); // 10000
    }
}

3.4 字段更新器:AtomicReferenceFieldUpdater

适用场景:原子更新对象的volatile字段,无需将字段包装为原子类(节省内存)。

使用条件

  • 字段必须是volatile类型;
  • 字段不能是static
  • 通过反射创建更新器实例。

实战案例:原子更新用户姓名

class User {
    volatile String name; // 必须是volatile
    // 构造器、getter省略
}

public class FieldUpdaterDemo {
    // 创建字段更新器
    private static final AtomicReferenceFieldUpdater<User, String> NAME_UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");

    public static void main(String[] args) {
        User user = new User("Bob");
        // 原子更新姓名
        boolean updated = NAME_UPDATER.compareAndSet(user, "Bob", "Alice");
        System.out.println("更新结果:" + updated); // true
        System.out.println("新姓名:" + user.name); // Alice
    }
}

3.5 累加器:LongAdder(高并发优化)

问题背景AtomicLong在高并发下所有线程竞争同一变量,CAS失败率高,自旋消耗CPU。LongAdder通过分段累加(将变量拆分为多个Cell)分散竞争,在统计时累加所有Cell值。

核心设计

  • base:无竞争时直接累加的基础值;
  • Cell[] cells:竞争时各线程分散到不同Cell累加;
  • @Contended:Cell类注解,避免伪共享(多个Cell共享缓存行导致失效)。

性能对比(10线程并发累加100万次):

实现方式 耗时(ms) 优势场景
AtomicLong 120 低并发、需精确实时值
LongAdder 35 高并发、统计场景(如QPS计数)

实战案例:高并发计数器

public class LongAdderDemo {
    public static void main(String[] args) throws InterruptedException {
        LongAdder adder = new LongAdder();
        Runnable task = () -> {
            for (int i = 0; i < 100000; i++) {
                adder.increment(); // 原子自增
            }
        };

        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(task);
            threads[i].start();
        }
        for (Thread t : threads) t.join();

        System.out.println("最终计数:" + adder.sum()); // 1000000
    }
}

四、Atomic类 vs 传统同步机制:性能与场景选择

特性 Atomic类(CAS) synchronized/Lock(锁机制)
实现原理 硬件指令(无锁) 对象监视器/AbstractQueuedSynchronizer
阻塞性 非阻塞(失败时自旋) 阻塞(未获取锁的线程进入等待队列)
性能 高并发下优势明显(无上下文切换) 低并发下简单,高并发下锁竞争导致性能下降
功能 仅支持单个变量原子操作 支持代码块/方法级原子操作
适用场景 计数器、累加器、状态标记 复杂逻辑同步(如转账、多变量更新)

选型建议

  • 简单变量原子操作(如计数):优先使用AtomicInteger/LongAdder
  • 高并发统计场景:使用LongAdder(性能优于AtomicLong);
  • 多变量或复杂逻辑同步:使用synchronizedReentrantLock
  • 对象引用原子更新:使用AtomicReference,需解决ABA问题时选择AtomicStampedReference

五、最佳实践与常见误区

5.1 最佳实践

  1. 避免过度依赖Atomic类:多变量操作需加锁,不可通过多个Atomic类组合实现;
  2. 高并发计数优先LongAdder:在QPS统计、请求量计数等场景,LongAdder性能优于AtomicLong
  3. 字段更新器的内存优化:对大量对象的字段进行原子更新时,使用AtomicReferenceFieldUpdater可减少内存占用(无需每个对象持有原子类实例);
  4. ABA问题防御:涉及对象引用更新且需感知中间变化时,必须使用AtomicStampedReference

5.2 常见误区

  • 认为Atomic类完全无锁:底层仍依赖CPU指令,但避免了线程阻塞;
  • 忽视volatile可见性:Atomic类的value字段通过volatile保证可见性,自定义原子类时需确保字段可见性;
  • LongAdder.sum()非原子操作sum()是瞬时快照,高并发下可能遗漏更新(适用于最终统计,非实时值)。

六、总结与展望

Java Atomic类通过CAS机制实现了高效的无锁并发控制,是并发编程中的重要工具。从基础的AtomicInteger到高并发优化的LongAdder,再到解决ABA问题的AtomicStampedReference,Atomic类家族覆盖了单变量原子操作的几乎所有场景。

未来趋势:随着硬件指令的发展,无锁编程将进一步提升并发性能。JDK后续版本可能会引入更多针对特定场景的原子类(如增强型累加器、更细粒度的内存控制),但核心思想仍将围绕CAS和分段竞争展开。

掌握Atomic类的原理与实践,不仅能提升并发程序性能,更能深入理解Java内存模型和并发设计思想,为构建高可靠、高性能的并发系统奠定基础。

参考资料

以下图示使用mermaid语法绘制,建议在支持mermaid的Markdown编辑器中查看:

图1:CAS操作原理流程图

相等
不相等
读取内存值V
比较V与预期值A
将V更新为新值B
放弃更新/重试
操作失败

图2:Atomic类继承关系图

«abstract»
Number
AtomicInteger
-int value
+int incrementAndGet()
+boolean compareAndSet(int expect, int update)
AtomicLong
-long value
+long incrementAndGet()
AtomicBoolean
-int value
+boolean compareAndSet(boolean expect, boolean update)
AtomicReference<T>
-T value
+boolean compareAndSet(T expect, T update)
AtomicStampedReference
AtomicMarkableReference

图3:ABA问题产生与解决示意图

线程1 线程2 内存 当前值=A (版本=1) 当前值=A (版本=1) 执行ABA操作 CAS(A,B,1,2) 成功 (值=B,版本=2) CAS(B,A,2,3) 成功 (值=A,版本=3) 尝试更新 CAS(A,C,1,2) 失败 (版本不匹配) 线程1 线程2 内存

图4:LongAdder分段累加机制

graph TD
    A[LongAdder] --> B[base值]
    A --> C[Cells数组]
    C --> D[Cell 0]
    C --> E[Cell 1]
    C --> F[Cell ...]
    C --> G[Cell n]
    
    Note over B,C: 无竞争时更新base<br>有竞争时分段更新Cells
    D --> H[value]
    E --> I[value]
    G --> J[value]
    
    K[sum()方法] --> L[累加base+所有Cell.value]

网站公告

今日签到

点亮在社区的每一天
去签到