java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java  AtomicReference 类

Java中的 AtomicReference类概览及实现方案

作者:探索java

在Java开发中,常常需要构建无锁的应用程序,以实现高性能的并发控制,本文给大家介绍Java中的AtomicReference类概览及实现方案,感兴趣的朋友跟随小编一起看看吧

一、引言

在 Java 开发中,常常需要构建无锁的应用程序,以实现高性能的并发控制。AtomicReference 就是它们中极其重要的一员。作为一种基于 CAS 机制实现的原实性工具,它在构建无锁队列、单例、上下文切换等场景中有着重要地位。

本文将随着一个体系化的进程,从基本概念到源码分析,展示 AtomicReference 在实际中如何做到 "精精不苦无锁同步"。

二、AtomicReference 概览

2.1 概念介绍

AtomicReference 是一个对应任意对象的原实性工具类,继承自 java.util.concurrent.atomic.AtomicReference<T>,提供类似于基本型的 atomicXXX 类,但对象化。

它内部基于 Unsafe 的 CAS 操作,能够精确地实现与目标对象的引用替换。

2.2 类结构快览

public class AtomicReference<V> implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    private volatile V value;
    // ... 重点方法看后续
}

类中主要是一个 volatile 字段 value,和基于 Unsafe 的 CAS 操作支持。

三、核心概念

3.1 CAS 原理

CAS(Compare-And-Swap)是一种无锁的原实性操作机制,其核心是把当前值和预期值比较,如果相等,则更新为新值。

实现简化为:

boolean compareAndSet(V expect, V update) {
    if (value == expect) {
        value = update;
        return true;
    } else {
        return false;
    }
}

实际中这个操作是依赖硬件持有的原实性 CPU 指令来完成的,在 Java 中通过 Unsafe 来调用。

3.2 Unsafe 类

Unsafe 是一个本地级类,提供相当不安全的内核操作,包括直接操作内存,操作类的内部字段值等。

四、AtomicReference 的基础用法

4.1 创建和基本操作

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        AtomicReference<String> ref = new AtomicReference<>("initial");
        boolean updated = ref.compareAndSet("initial", "updated");
        System.out.println("Update successful: " + updated);
        System.out.println("Current value: " + ref.get());
    }
}

输出:

Update successful: true
Current value: updated

五、源码深度解析

在深入理解 AtomicReference 的行为机制之前,有必要从源码级别进行一层层剖析。本章节将覆盖类结构、关键方法、内存语义以及 Unsafe.compareAndSwapObject 方法的底层实现。

5.1 类定义与字段分析

源码路径:java.util.concurrent.atomic.AtomicReference

public class AtomicReference<V> implements java.io.Serializable {
    private static final long serialVersionUID = -1848883965231344442L;
    // Unsafe 是用于执行底层 CAS 操作的核心类
    private static final sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
    // 用于存储 value 字段的偏移量,在初始化时通过反射获取
    private static final long valueOffset;
    // 关键字段,volatile 保证可见性
    private volatile V value;
    static {
        try {
            // valueOffset 的计算,核心反射操作
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    public AtomicReference(V initialValue) {
        value = initialValue;
    }
    public AtomicReference() {
    }

说明:

5.2 核心方法解析

public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
public final void set(V newValue) {
    value = newValue;
}
public final void lazySet(V newValue) {
    unsafe.putOrderedObject(this, valueOffset, newValue);
}
public final V get() {
    return value;
}
public final V getAndSet(V newValue) {
    while (true) {
        V current = get();
        if (compareAndSet(current, newValue))
            return current;
    }
}

方法说明:

5.3 内存语义分析

内存屏障说明:

compareAndSet:
  LoadLoad + LoadStore + StoreStore + StoreLoad(全屏障)
lazySet:
  StoreStore(写屏障)

这也是为什么在高并发但不要求严格即时可见性的场景下,lazySet 更高效。

5.4 Unsafe.compareAndSwapObject 深度剖析

该方法定义如下:

public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);

其是 native 方法,最终调用 JVM 内部封装的 CPU 指令实现(如 x86 架构下的 CMPXCHG)。其关键逻辑:

🚀 实质:此操作具备原子性,不会被线程上下文切换打断。

CPU 层级说明(以 x86 为例)

在底层,JVM 会借助 CPU 提供的原子指令实现上述逻辑,比如:

lock cmpxchg r/m32, r32  // 带锁的比较交换,保证总线级别原子

这确保了在多核环境中即使多个线程同时竞争更新,依然可以避免数据竞争。

5.5 JVM 字节码观察

我们通过 javap -c -v 查看 compareAndSet 调用层次:

javap -c -v java.util.concurrent.atomic.AtomicReference

输出片段如下:

  public final boolean compareAndSet(java.lang.Object, java.lang.Object);
    Code:
       0: getstatic     #16                 // Field unsafe:Lsun/misc/Unsafe;
       3: aload_0
       4: getstatic     #18                 // Field valueOffset:J
       7: aload_1
       8: aload_2
       9: invokevirtual #24                 // Method sun/misc/Unsafe.compareAndSwapObject
      12: ireturn

从字节码角度也能看出 compareAndSet 调用是对 Unsafe 对象方法的直接转发。

小结

六、常见问题及解决方案

AtomicReference 虽然为无锁编程提供了便捷手段,但在实践中仍然存在若干值得注意的问题。特别是在高并发环境下,如果理解不当,可能会引入隐蔽的并发 bug。本章节总结了实际使用中遇到的典型问题及对应解决策略。

6.1 问题一:ABA 问题

问题描述:

CAS 操作基于对象的引用地址进行比较,若地址值相同则视为“未变”,这就带来一个典型并发陷阱:ABA 问题。

定义:

线程 A 读取变量为 A,线程 B 将其从 A 改为 B 再改回 A,线程 A 发现当前还是 A,于是操作成功,但其实已经发生过变化。

示例:

AtomicReference<String> ref = new AtomicReference<>("A");
Thread t1 = new Thread(() -> {
    String prev = ref.get(); // 读取到 A
    sleep(100);
    boolean success = ref.compareAndSet(prev, "C");
    System.out.println("T1 CAS: " + success); // 可能为 true,但实际中间已经被改动过
});
Thread t2 = new Thread(() -> {
    ref.compareAndSet("A", "B");
    ref.compareAndSet("B", "A");
});
t1.start();
t2.start();

风险:

虽然最终值为 "A",但实际上经历了变化。CAS 操作未感知这些中间变更,可能导致数据一致性问题。

解决方案:AtomicStampedReference

为了解决 ABA 问题,JDK 提供了 AtomicStampedReference 类。它不仅保存值,还维护一个版本号(stamp),每次更新都需同时更新 stamp,从而感知变量是否真正变化过。

示例:

AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
Thread t1 = new Thread(() -> {
    int[] stamp = new int[1];
    String prev = ref.get(stamp);
    sleep(100);
    boolean success = ref.compareAndSet(prev, "C", stamp[0], stamp[0] + 1);
    System.out.println("T1 CAS with stamp: " + success);
});
Thread t2 = new Thread(() -> {
    int[] stamp = new int[1];
    String curr = ref.get(stamp);
    ref.compareAndSet(curr, "B", stamp[0], stamp[0] + 1);
    ref.compareAndSet("B", "A", stamp[0] + 1, stamp[0] + 2);
});
t1.start();
t2.start();

✅ 由于 stamp 不同,即使值回到 "A",也能检测到版本不一致,防止 ABA 问题。

6.2 问题二:引用本身原子,不代表对象状态原子

AtomicReference<T> 仅保证对象引用的更新是原子的,并不能保证引用对象内部的字段或状态是线程安全的。

示例:

class User {
    String name;
    int age;
}
AtomicReference<User> ref = new AtomicReference<>(new User());
ref.get().age++; // 非原子操作

风险:

如果多个线程同时修改 ref.get().age++,依然可能引发竞态条件。

解决方案:

while (true) {
    User oldUser = ref.get();
    User newUser = new User();
    newUser.name = oldUser.name;
    newUser.age = oldUser.age + 1;
    if (ref.compareAndSet(oldUser, newUser)) break;
}

6.3 问题三:频繁 CAS 导致性能下降

CAS 是乐观锁思想,但在高竞争场景下,多次失败会导致性能问题。

解决策略:

小结

问题现象解决方式
ABA 问题值恢复原样但中途被修改使用 AtomicStampedReference
引用更新原子但状态不安全内部字段可能并发读写冲突使用不可变对象整体替换
CAS 自旋性能差多线程竞争失败导致 CPU 消耗上升增加退避策略 / 限制自旋次数

在实际应用中,合理选择原子类并避免过度依赖单一机制是保障系统健壮性的关键。

到此这篇关于Java 中的 AtomicReference 类及其实现的文章就介绍到这了,更多相关Java AtomicReference 类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文