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

 更新时间:2025年09月15日 12:01:39   作者:探索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() {
    }

说明:

  • value 是被保护的实际引用,必须是 volatile,以确保在并发线程之间可见。
  • valueOffset 是通过反射拿到的 value 字段在对象内存结构中的偏移量。
  • 所有 CAS 操作都基于 valueOffset

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;
    }
}

方法说明:

  • compareAndSet: 基于 CAS 的原子条件更新,是核心方法。
  • set: 普通赋值操作,具备 volatile 语义。
  • lazySet: 有序写入,适用于延迟可见的情况,性能更优。
  • getAndSet: 实现方式为循环 CAS。

5.3 内存语义分析

  • volatile 语义:保证读写操作的可见性,防止指令重排。
  • compareAndSet 的语义:具备 volatile 的读+写语义,同时含有内存屏障(full fence)。
  • lazySet 语义:具备“store-store barrier”,只保证新值最终会被线程看到,但不强制立即生效。

内存屏障说明:

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)。其关键逻辑:

  • 比较对象 o 中偏移量为 offset 的字段值是否等于 expected
  • 如果相等,则用 x 替换原值,返回 true
  • 否则不做修改,返回 false

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

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 实现基于 Unsafe 的 CAS 操作,绕过 synchronized 实现高性能原子更新。
  • valueOffset 是通过反射获取的内存偏移,用于精确定位对象字段。
  • compareAndSet 调用的是 JVM 层原子指令(cmpxchg),确保并发安全。
  • volatilelazySet 提供不同程度的内存可见性策略,需根据具体业务场景选择。

六、常见问题及解决方案

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++,依然可能引发竞态条件。

解决方案:

  • 使用 AtomicReference<User> 实现整体替换(不可变类方案)
  • 或封装更新逻辑,通过 CAS 替换整个对象:
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 是乐观锁思想,但在高竞争场景下,多次失败会导致性能问题。

解决策略:

  • 使用 LongAdderStampedLock 等替代方案
  • 或者使用回退策略、自旋上限等优化手段

小结

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

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

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

相关文章

  • SpringBoot中使用websocket出现404的解决方法

    SpringBoot中使用websocket出现404的解决方法

    在Springboot中使用websocket时,本地开发环境可以正常运行,但部署到服务器环境出现404问题,所以本文小编讲给大家详细介绍一下SpringBoot中使用websocket出现404的解决方法,需要的朋友可以参考下
    2023-09-09
  • RocketMQ顺序消息的原理与特点

    RocketMQ顺序消息的原理与特点

    RocketMQ作为一款纯java、分布式、队列模型的开源消息中间件,支持事务消息、顺序消息、批量消息、定时消息、消息回溯等,本篇我们了解如何实现顺序消息的原理与特点
    2023-02-02
  • Java static关键字详细解析

    Java static关键字详细解析

    这篇文章主要介绍了Java static关键字详细解析,java中的static关键字主要用于内存管理,可以用在变量、方法、代码块和嵌套类中。更多相关介绍,需要的小伙伴可以参考一下
    2022-08-08
  • 基于Nacos实现Spring Cloud Gateway实现动态路由的方法

    基于Nacos实现Spring Cloud Gateway实现动态路由的方法

    这篇文章主要介绍了基于Nacos实现Spring Cloud Gateway实现动态路由的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • Spring boot 跳转到jsp页面的实现方法

    Spring boot 跳转到jsp页面的实现方法

    本篇文章主要介绍了Spring boot 跳转到jsp页面的实现方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • java使用itext导出PDF文本绝对定位(实现方法)

    java使用itext导出PDF文本绝对定位(实现方法)

    下面小编就为大家带来一篇java使用itext导出PDF文本绝对定位(实现方法)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Spring学习教程之AOP模块的概述

    Spring学习教程之AOP模块的概述

    AOP 从功能的角度来讲,可能看作OOP编程方式的一种补充,提供了一种不同的代码或者系统组织方式,下面这篇文章主要给大家介绍了关于Spring学习教程之AOP模块的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2018-05-05
  • JVM教程之内存管理和垃圾回收(三)

    JVM教程之内存管理和垃圾回收(三)

    这篇文章主要介绍了JVM学习笔记的第三篇内存管理和垃圾回收,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • Java后台Controller实现文件下载操作

    Java后台Controller实现文件下载操作

    这篇文章主要介绍了Java后台Controller实现文件下载操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • springboot swagger 接口文档分组展示功能实现

    springboot swagger 接口文档分组展示功能实现

    这篇文章主要介绍了springboot swagger 接口文档分组展示功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-03-03

最新评论