AtomicReferenceFieldUpdater用于对volatile引用字段原子更新,适合懒加载缓存,比AtomicReference更省内存;要求字段volatile、非static、非final,且updater需同包或public访问。

在 Java 并发编程中,AtomicReferenceFieldUpdater 是一种轻量级的无锁工具,适用于对已有对象的某个 volatile 引用字段进行原子更新,特别适合实现“懒加载 + 无锁更新”的缓存模式——即首次访问时初始化缓存,后续直接返回,且不依赖 synchronized 或 ReentrantLock。
为什么用 AtomicReferenceFieldUpdater 而不是 AtomicReference?
当你无法或不希望把整个缓存字段封装成独立的 AtomicReference 实例(比如已有类结构固定、不想增加包装对象、或为减少内存开销),AtomicReferenceFieldUpdater 允许你直接对类中已声明为 volatile 的引用字段做 CAS 更新,零额外对象分配,更省内存、更贴近原始设计。
注意三点限制:
- 目标字段必须是
volatile、非static、非final的引用类型; - updater 必须通过
newUpdater()静态方法获取,且泛型参数需严格匹配类和字段类型; - 字段访问权限需为
public或与 updater 创建类在同一包内(推荐设为protected或包级私有 + 同包使用)。
定义缓存字段与 updater 实例
以一个典型配置类为例,缓存一个解析后的 JSON 配置对象:
立即学习“Java免费学习笔记(深入)”;
public class ConfigHolder {
// 必须是 volatile、非 static、非 final 的引用字段
volatile Config cachedConfig;
// 声明静态 updater(推荐 private static final)
private static final AtomicReferenceFieldUpdater<ConfigHolder, Config> UPDATER =
AtomicReferenceFieldUpdater.newUpdater(ConfigHolder.class, Config.class, "cachedConfig");
public Config getConfig() {
Config config = cachedConfig;
if (config == null) {
config = loadConfig(); // 从文件/网络加载
// CAS 设置:仅当字段仍为 null 时才更新,避免重复初始化
if (UPDATER.compareAndSet(this, null, config)) {
// 成功写入,当前线程完成初始化
} else {
// 失败说明其他线程已设置,直接读取最新值
config = cachedConfig;
}
}
return config;
}
private Config loadConfig() {
// 模拟耗时加载(如 Jackson 解析)
return new Config();
}
}
处理初始化异常:避免缓存 null 或中间态
上面例子有个隐患:若 loadConfig() 抛异常,cachedConfig 仍为 null,下次调用又会重试——可能引发雪崩。更健壮的做法是用“哨兵对象”标记失败状态:
- 定义一个私有静态
Config NULL_PLACEHOLDER = new Config() {}(或用Optional.empty()包装); - 加载失败时 CAS 写入占位符,后续直接抛出异常或返回默认值;
- CAS 前先检查是否为
null或占位符,跳过已失败路径。
示例片段:
private static final Config FAILED = new Config() {}; // 哨兵
public Config getConfig() {
Config config = cachedConfig;
if (config == null || config == FAILED) {
config = loadConfigSafely();
if (config != null) {
UPDATER.compareAndSet(this, config == FAILED ? FAILED : null, config);
} else {
UPDATER.compareAndSet(this, null, FAILED);
}
}
return config == FAILED ? null : config;
}
注意事项与常见陷阱
字段修饰符必须严格匹配:哪怕写成 private volatile Config,updater 在不同包下也无法访问(除非加 sun.misc.Unsafe 黑科技,不推荐)。建议将 updater 类与目标类放在同一包,并把字段设为 package-private(即不加修饰符)。
不能用于子类字段继承场景:updater 绑定的是声明字段的类,即使子类继承该字段,也不能用父类 updater 更新子类实例中的同名字段(运行时报 IllegalArgumentException)。
慎用于高频更新场景:CAS 失败会重试读取,若竞争极激烈(如每毫秒数百次调用),不如直接用 ConcurrentHashMap.computeIfAbsent 或双重检查锁(DCL)更清晰可控。



















