当前位置: 首页 > news >正文

Netty中AbstractReferenceCountedByteBuf对AtomicIntegerFieldUpdater的使用

AtomicIntegerFieldUpdater使用

java.util.concurrent.atomic.AtomicIntegerFieldUpdater 是 Java 并发包中一个非常强大的工具,它允许你以原子方式更新指定对象的 volatile int 字段,而无需使用锁。这在实现高性能、非阻塞的并发算法时非常有用,就像 Netty 在其 ByteBuf 引用计数管理中所做的那样。


1. AtomicIntegerFieldUpdater 的核心作用

AtomicIntegerFieldUpdater 的主要目的是实现字段级别的 CAS (Compare-And-Swap) 操作

  • 目标字段: 它操作的是类的 volatile int 字段volatile 关键字是强制性的,因为它保证了字段在多线程间的可见性,并且阻止了编译器和处理器进行可能导致并发问题的重排序。
  • 原子性: 尽管它不使用传统的 synchronized 关键字或 Lock 接口,但它通过底层的硬件 CAS 指令来保证操作的原子性。这意味着读取、比较和更新这三个步骤是一个不可中断的单一操作。
  • 非阻塞: 当多个线程尝试更新同一个字段时,只有一个线程会成功,其他线程会失败并重试(自旋)。这种“乐观锁”的机制避免了线程阻塞和上下文切换的开销,从而在并发量高时提供更好的性能。

2. 为什么需要 AtomicIntegerFieldUpdater

传统的 AtomicInteger 只能操作一个独立的 int 变量。但如果你有一个类,它内部的一个 int 字段需要被原子更新,而你又不想为整个类或方法加锁,AtomicIntegerFieldUpdater 就派上用场了。

例如,在 Netty 的 AbstractReferenceCountedByteBuf 中,refCnt(引用计数)是一个 ByteBuf 实例的内部字段。Netty 希望能在不阻塞 ByteBuf 实例的情况下,原子地修改它的 refCnt,以最大化性能。AtomicIntegerFieldUpdater 正好满足了这个需求。


3. AtomicIntegerFieldUpdater 的基本用法

使用 AtomicIntegerFieldUpdater 通常包括以下几个步骤:

步骤 1: 目标字段必须是 volatile int

这是最关键的前提。你要操作的字段必须声明为 public volatile intprotected volatile intprivate volatile int(如果 Updater 也在同一个类中),并且不能是 staticfinal

Java

public class MyClass {// 必须是 volatile int 类型private volatile int myIntField;// ... 其他代码 ...
}
步骤 2: 创建 AtomicIntegerFieldUpdater 实例

Updater 是通过静态工厂方法 newUpdater() 来创建的。

Java

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;public class MyClass {private volatile int myIntField;// 1. 创建 AtomicIntegerFieldUpdater 实例//    参数1: 要操作的对象的 Class 类型//    参数2: 要操作的字段的名称 (字符串)private static final AtomicIntegerFieldUpdater<MyClass> UPDATER =AtomicIntegerFieldUpdater.newUpdater(MyClass.class, "myIntField");public MyClass(int initialValue) {this.myIntField = initialValue;}// ... 其他方法 ...
}

注意事项:

  • newUpdater 方法是线程安全的,通常只在类加载时执行一次,所以把它定义为 static final 字段是最佳实践。
  • 字段名称(字符串)必须准确匹配。如果写错,会在运行时抛出 NoSuchFieldExceptionIllegalArgumentException
步骤 3: 使用 Updater 进行原子操作

创建 Updater 实例后,就可以使用它提供的方法来执行原子操作了。这些方法需要传入目标对象实例作为第一个参数,因为 Updater 是通用的,需要知道具体操作哪个对象的字段。

Java

public class MyClass {private volatile int myIntField;private static final AtomicIntegerFieldUpdater<MyClass> UPDATER =AtomicIntegerFieldUpdater.newUpdater(MyClass.class, "myIntField");public MyClass(int initialValue) {this.myIntField = initialValue;}// 获取字段当前值public int getMyIntField() {return UPDATER.get(this);}// 原子地设置字段值public void setMyIntField(int newValue) {UPDATER.set(this, newValue);}// 原子地增加或减少字段值public int incrementAndGet() {return UPDATER.incrementAndGet(this); // myIntField++ 并返回新值}public int decrementAndGet() {return UPDATER.decrementAndGet(this); // myIntField-- 并返回新值}// 核心的 CAS 操作:比较并设置// 如果 myIntField 当前值等于 expect,则将其设置为 update,并返回 true;否则返回 false。public boolean compareAndSet(int expect, int update) {return UPDATER.compareAndSet(this, expect, update);}// 原子地累加指定值并返回旧值public int getAndAdd(int delta) {return UPDATER.getAndAdd(this, delta);}// 原子地设置新值并返回旧值public int getAndSet(int newValue) {return UPDATER.getAndSet(this, newValue);}public static void main(String[] args) {MyClass obj = new MyClass(0);// 示例使用System.out.println("Initial value: " + obj.getMyIntField()); // 0obj.incrementAndGet();System.out.println("After increment: " + obj.getMyIntField()); // 1obj.setMyIntField(10);System.out.println("After set: " + obj.getMyIntField()); // 10boolean success = obj.compareAndSet(10, 15); // 期望是10,更新为15System.out.println("CAS success: " + success + ", current value: " + obj.getMyIntField()); // true, 15success = obj.compareAndSet(10, 20); // 期望是10,但当前是15,所以失败System.println("CAS success: " + success + ", current value: " + obj.getMyIntField()); // false, 15}
}

4. AtomicIntegerFieldUpdater 在 Netty 中的应用

在 Netty 的 AbstractReferenceCountedByteBuf 中,AtomicIntegerFieldUpdater 被用于原子地管理 refCnt(引用计数)字段

Java

// netty/buffer/AbstractReferenceCountedByteBuf.java (简化版)public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {// refCnt 字段,volatile 保证可见性,private 封装实现private volatile int refCnt;// 创建 Updater 实例,操作 this 对象的 refCnt 字段private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> REFCNT_UPDATER =AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");protected AbstractReferenceCountedByteBuf(int maxCapacity) {super(maxCapacity);// 初始化 refCnt 为 1// 注意这里不是通过 UPDATER.set(this, 1),而是直接设置,// 因为在构造函数中,对象还未发布给其他线程,不存在并发问题refCnt = 1;}@Overridepublic final ByteBuf retain(int increment) {// ... 参数校验 ...for (;;) { // 自旋循环,直到 CAS 成功int refCnt = this.refCnt; // 读取当前值// ... 各种校验 (如是否已释放, 是否溢出) ...if (REFCNT_UPDATER.compareAndSet(this, refCnt, refCnt + increment)) { // 尝试 CAS 更新return this; // 更新成功,跳出循环}// 失败则继续循环重试}}@Overridepublic final boolean release(int decrement) {// ... 参数校验 ...for (;;) { // 自旋循环,直到 CAS 成功int refCnt = this.refCnt; // 读取当前值// ... 各种校验 (如是否小于减量) ...if (REFCNT_UPDATER.compareAndSet(this, refCnt, refCnt - decrement)) { // 尝试 CAS 更新if (refCnt == decrement) { // 如果引用计数归零deallocate(); // 调用抽象方法释放资源return true;}return false; // 更新成功,但未归零}// 失败则继续循环重试}}protected abstract void deallocate(); // 留给子类实现具体的内存释放逻辑
}

解析:

  • refCnt 为什么是 private volatile int private 封装了实现细节,不允许外部直接修改,只能通过 retain()release()volatile 保证了所有线程都能看到最新的 refCnt 值。
  • 构造函数中的初始化: 在构造函数中,refCnt = 1; 并没有使用 Updater。这是因为在对象构造期间,对象还没有“发布”给其他线程,不存在并发访问的风险,直接赋值是最快的。
  • for (;;) 循环: 无论是 retain() 还是 release(),它们都包裹在一个 for (;;) 循环中。这是典型的 自旋 (Spinning) 模式。如果 compareAndSet 操作失败(说明在读取 refCnt 到尝试更新之间,有其他线程修改了它),线程不会被阻塞,而是会立即重新读取最新值并再次尝试,直到成功为止。
  • deallocate() 的触发:release() 方法中,当 compareAndSet 成功并且新的 refCnt 值等于 0 时,就会调用抽象的 deallocate() 方法,由具体的 ByteBuf 子类来执行实际的内存释放(如归还到内存池或释放直接内存)。

5. 优点与适用场景

  • 高性能: 基于 CAS 的非阻塞算法通常比基于锁的算法具有更高的并发性能,尤其是在竞争不激烈的情况下。
  • 细粒度控制: 允许对单个字段进行原子操作,而不需要锁定整个对象或代码块。
  • 减少 GC 压力: 避免了锁带来的线程阻塞和上下文切换,从而降低了系统开销,间接有助于降低 GC 压力。

适用场景:

  • 需要原子更新对象内部的 volatile int/long/boolean 字段,且不希望引入传统锁的开销。
  • 实现自定义的非阻塞数据结构。
  • 计数器、状态标志等字段的并发更新。

6. 潜在的缺点与注意事项

  • ABA 问题: 虽然 CAS 保证了原子性,但它存在 ABA 问题。即一个值从 A 变为 B,再变回 A,CAS 可能会误认为没有发生变化。对于引用计数这种只增不减然后归零的场景,ABA 问题通常不是致命的。但对于其他场景,可能需要 AtomicStampedReferenceAtomicMarkableReference
  • 自旋开销: 如果并发竞争非常激烈,自旋可能会消耗大量的 CPU 资源,导致“忙等”。在这种极端情况下,传统的阻塞锁(如 ReentrantLock)可能表现更好,因为它能让失败的线程挂起而不是持续消耗 CPU。
  • 字段限制: 只能操作 volatile 修饰的非 static、非 finalintlongboolean 字段(对应 AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater)。
  • 反射开销: newUpdater 的创建内部涉及反射,但这通常只发生一次,所以可以忽略。
http://www.dtcms.com/a/293370.html

相关文章:

  • 威胁情报:Solana 开源机器人盗币分析
  • Automotive SPICE
  • git的版本冲突
  • 大模型——Data Agent:超越 BI 与 AI 的边界
  • 用ESP32打造全3D打印四驱遥控车:无需APP的Wi-Fi控制方案
  • 从0开始的中后台管理系统-2
  • 课题学习笔记2——中华心法问答系统
  • 汽车行业数字化——解读52页汽车设计制造一体化整车产品生命周期PLM解决方案【附全文阅读】
  • 记录更新时间用java的new date还是数据库的now
  • 深入理解 C 语言数据类型:从内存到应用的全面解析
  • CAN基础知识 - 进阶版
  • 消息推送功能设计指南:精准触达与用户体验的平衡之道
  • Spring Boot 中集成ShardingSphere-JDBC的基本使用
  • Kibana报错[security_exception] current license is non-compliant for [security]
  • HCIA/IP(一二章)笔记
  • TTL+日志的MDC实现简易链路追踪
  • 强化学习理论
  • 计算机是怎么样工作的
  • 在 Ubuntu 22.04 上安装并优化 Nginx nginx入门操作 稍难,需要有一定理论 多理解 多实践
  • Class13预测房价代码
  • Google Gemini 体验
  • 从零开始学CTF(第二十五期)
  • 万界星空科技铜线/漆包线行业智能化MES系统解决方案
  • postgresql导入导出数据;pg_restore: error: did not find magic string in file header
  • 基础算法思想(递归篇)
  • 厚铜板载流革命与精密压合工艺——高可靠性PCB批量制造的新锚点
  • Android AppCompat:实现Material Design向后兼容的终极指南
  • IDEA-通过IDEA导入第三方的依赖包
  • [Semantic Seg][KD]FreeKD: Knowledge Distillation via Semantic Frequency Prompt
  • Bigemap Pro自动获取面要素所属行政区划