news 2026/9/16 9:26:05

Java并发编程:AtomicReference实现多变量原子操作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java并发编程:AtomicReference实现多变量原子操作

1. 理解CAS的单变量限制

在并发编程中,Compare-And-Swap(CAS)是最基础的原子操作之一。CAS操作包含三个关键参数:内存位置(V)、预期原值(A)和新值(B)。当且仅当内存位置V的值等于预期值A时,处理器才会将该位置的值更新为B,否则不执行任何操作。整个操作过程是原子的,不会被其他线程打断。

Java中的AtomicInteger、AtomicLong等原子类正是基于CAS实现的。例如AtomicInteger的incrementAndGet()方法:

public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }

这种实现方式虽然高效,但存在一个明显的限制:它只能保证单个变量的原子操作。当我们需要对多个共享变量进行原子更新时,简单的CAS就无法满足需求了。

2. 多共享变量原子操作的挑战

假设我们有一个账户类,需要同时原子性地更新余额和最后修改时间:

class Account { private int balance; private long lastUpdateTime; // 需要原子更新的方法 public void update(int amount) { this.balance += amount; this.lastUpdateTime = System.currentTimeMillis(); } }

在这种场景下,我们会遇到几个典型问题:

  1. 竞态条件:两个线程可能同时读取balance的旧值,导致更新丢失
  2. 不一致状态:一个线程更新了balance但还未更新lastUpdateTime时,另一个线程可能读取到不一致的状态
  3. 锁开销:使用synchronized或Lock虽然能解决问题,但会带来性能损耗

3. AtomicReference的解决方案

3.1 基本使用模式

AtomicReference允许我们以原子方式更新对象引用。解决多变量原子操作的关键在于使用不可变对象模式:

class AccountState { final int balance; final long lastUpdateTime; public AccountState(int balance, long lastUpdateTime) { this.balance = balance; this.lastUpdateTime = lastUpdateTime; } } AtomicReference<AccountState> accountRef = new AtomicReference<>();

更新操作时,我们创建新的不可变对象:

public void update(int amount) { AccountState current, newState; do { current = accountRef.get(); newState = new AccountState( current.balance + amount, System.currentTimeMillis() ); } while (!accountRef.compareAndSet(current, newState)); }

3.2 实现原理分析

这种模式之所以能工作,依赖于几个关键特性:

  1. 不可变对象:一旦创建,状态就不会改变,确保线程安全
  2. 引用原子性:AtomicReference保证引用更新的原子性
  3. CAS重试机制:当并发冲突时,通过循环重试确保最终成功

4. 实战案例:银行转账系统

让我们通过一个完整的银行转账示例来演示这种技术的实际应用:

class TransferSystem { static class Account { private final String id; private final AtomicReference<State> state; static class State { final BigDecimal balance; final long version; State(BigDecimal balance, long version) { this.balance = balance; this.version = version; } } public Account(String id, BigDecimal initialBalance) { this.id = id; this.state = new AtomicReference<>(new State(initialBalance, 0)); } public boolean transferTo(Account target, BigDecimal amount) { if (amount.compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("Amount must be positive"); } while (true) { State current = state.get(); if (current.balance.compareTo(amount) < 0) { return false; // 余额不足 } State newState = new State( current.balance.subtract(amount), current.version + 1 ); if (state.compareAndSet(current, newState)) { target.receive(amount); return true; } // CAS失败,重试 } } private void receive(BigDecimal amount) { while (true) { State current = state.get(); State newState = new State( current.balance.add(amount), current.version + 1 ); if (state.compareAndSet(current, newState)) { return; } } } } }

这个实现具有以下特点:

  1. 使用版本号解决ABA问题
  2. 金额使用BigDecimal避免浮点数精度问题
  3. 转账操作是原子的,要么完全成功,要么完全失败
  4. 无锁设计,高并发场景下性能更好

5. 性能优化技巧

5.1 减少对象创建开销

频繁创建不可变对象可能带来GC压力。可以通过以下方式优化:

  1. 对象池:对常用状态值使用对象池
  2. 享元模式:对部分不变的状态使用共享对象
  3. 值类型:在Java 14+中使用record类减少开销
// Java 14+ record类示例 record AccountState(BigDecimal balance, long version) {} // 使用示例 AtomicReference<AccountState> ref = new AtomicReference<>( new AccountState(BigDecimal.ZERO, 0) );

5.2 退避策略优化

在高竞争场景下,简单的忙等待(busy-wait)可能浪费CPU资源。可以引入退避策略:

public boolean transferWithBackoff(Account target, BigDecimal amount) { int retries = 0; long backoffTime = 1; // 初始退避时间1ms while (retries < MAX_RETRIES) { State current = state.get(); // ... 省略检查逻辑 if (state.compareAndSet(current, newState)) { target.receive(amount); return true; } // 指数退避 try { Thread.sleep(backoffTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } backoffTime = Math.min(backoffTime * 2, MAX_BACKOFF); retries++; } return false; }

6. 常见问题与解决方案

6.1 ABA问题

虽然AtomicReference本身不解决ABA问题,但可以通过以下方式处理:

  1. 版本号:在状态对象中加入版本号字段
  2. 时间戳:使用修改时间作为辅助判断
  3. AtomicStampedReference:Java提供的带版本号的引用类
// 版本号解决方案示例 class VersionedState<T> { final T value; final long version; // 构造函数等... } AtomicReference<VersionedState<Account>> ref = new AtomicReference<>();

6.2 内存可见性

即使使用AtomicReference,也需要注意:

  1. 状态对象的所有字段都应该是final的
  2. 如果状态对象包含对其他可变对象的引用,需要额外同步
  3. 考虑使用volatile修饰关键字段

6.3 死锁风险

虽然无锁算法避免了传统死锁,但仍可能遇到活锁问题:

  1. 多个线程持续重试相同的操作
  2. 使用随机退避时间减少冲突
  3. 设置最大重试次数,超过后转为其他策略

7. 与其他方案的对比

7.1 对比synchronized

特性AtomicReference方案synchronized
并发度
阻塞非阻塞阻塞
内存开销每个对象额外引用每个对象监视器
适用场景高竞争短操作低竞争长操作
死锁风险

7.2 对比Lock

特性AtomicReference方案Lock
实现复杂度
公平性不支持可配置
条件变量不支持支持
可中断性需自行实现内置支持
适用场景简单原子操作复杂同步逻辑

8. 最佳实践建议

  1. 保持状态对象简单:理想情况下只包含基本类型和不可变对象
  2. 最小化原子操作范围:只将真正需要原子更新的部分放入AtomicReference
  3. 考虑不变性:确保状态对象是不可变的,所有字段设为final
  4. 监控竞争情况:记录CAS失败次数,评估系统并发压力
  5. 备选方案:当竞争激烈时,考虑回退到锁方案
// 监控示例 class MonitoredAtomicReference<T> { private final AtomicReference<T> ref = new AtomicReference<>(); private final AtomicLong failureCount = new AtomicLong(); public boolean compareAndSet(T expect, T update) { boolean success = ref.compareAndSet(expect, update); if (!success) { failureCount.incrementAndGet(); } return success; } public long getFailureCount() { return failureCount.get(); } }

9. 扩展应用场景

这种技术不仅适用于金融场景,还可以应用于:

  1. 配置管理:原子性地切换整个系统配置
  2. 状态机实现:实现无锁状态转换
  3. 缓存系统:原子性地更新缓存条目
  4. 游戏开发:处理玩家状态的并发更新
// 游戏玩家状态示例 class Player { private final AtomicReference<PlayerState> state; static class PlayerState { final Position position; final Health health; final Inventory inventory; // 构造函数等... } public void move(Position newPosition) { PlayerState current, newState; do { current = state.get(); newState = new PlayerState( newPosition, current.health, current.inventory ); } while (!state.compareAndSet(current, newState)); } }

10. Java内存模型考量

使用AtomicReference时,需要理解Java内存模型(JMM)的几个关键点:

  1. happens-before关系:成功的CAS操作会建立happens-before关系
  2. 可见性保证:AtomicReference保证引用的可见性,但不保证引用对象内部字段的可见性
  3. 重排序限制:JVM会插入适当的内存屏障

对于包含复杂状态的对象,确保:

  1. 所有字段在构造函数中完全初始化
  2. 状态对象正确发布(通过final字段或安全发布机制)
  3. 避免在状态对象中泄露this引用
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 9:25:19

MOS 2016大师级认证备考全指南:从科目解析到实战技巧

1. 为什么这么多年了&#xff0c;我还在推荐MOS 2016大师级1.1 这不是“过时教程”&#xff0c;而是Office操作能力的真正标尺先劝退一波人&#xff1a;如果你只是想“混个证”&#xff0c;那MOS 2016大师级不适合你。它不是刷选择题就能过的考试&#xff0c;全程都是真实软件环…

作者头像 李华
网站建设 2026/9/16 9:23:28

智在记录等5款录音转文字实测,让 Codex 走 TaoToken 出对比结论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 9:23:00

怎么判断一所国际学校管得严不严?2026苏州国际高中管理严格度观察:从细节看懂学校管理底色+苏州阿德科特服务能力解读+择校避坑FAQ

当了爹妈之后&#xff0c;聊天话题三句不离孩子。尤其是孩子到了初中高年级&#xff0c;要不要转轨国际教育&#xff1f;苏州那么多国际高中&#xff0c;到底哪家管得严&#xff1f;这个问题&#xff0c;几乎成了家长圈里的高频话题。很多人觉得&#xff0c;国际学校嘛&#xf…

作者头像 李华
网站建设 2026/9/16 9:22:55

GESP C++八级的自测打卡表

这就为你整理 GESP C八级必考词汇 速记卡片自测打卡表&#xff0c;完全适配四年级信奥选手节奏&#xff0c;可直接打印使用&#xff0c;每天5分钟就能完成自测&#xff1a; &#x1f4cb; GESP C八级 必考词汇自测打卡表 共7天任务&#xff0c;覆盖18张核心速记卡片&#xff…

作者头像 李华
网站建设 2026/9/16 9:22:44

无服务器应用开发全景指南:从核心概念到工具链与成本优化

前两天有个朋友在群里发了个截图&#xff0c;说把Auto Scaling组里的期望实例数调成了0&#xff0c;想着没有流量了肯定不产生费用&#xff0c;结果月底账单下来还是傻了眼。我看了眼他的资源清单&#xff0c;回答他&#xff1a;你ASG是没跑实例了&#xff0c;可你那还挂着个NA…

作者头像 李华
网站建设 2026/9/16 9:22:33

Top20录取占比升至25%:26Fall研究生申请新打法

摘要26Fall研究生申请季&#xff0c;QS Top20院校录取占比由25Fall的17.6%提升至25.0%&#xff0c;等于每四枚录取中就有一枚来自QS前20院校&#xff1b;同期QS Top50占比为50.0%&#xff0c;100及其他院校的录取由7枚增至12枚。高位项目占比上升与录取边界同步延伸&#xff0c…

作者头像 李华