纲手的决断:血泪与荣耀铸就的医疗传奇

核心内容摘要

跨越五百年的惊鸿一瞥:当冉冉学姐遇见唐伯虎,美学界的一次“破次元”大爆炸
牌局中的心跳:当博弈遇上默契,扑克桌上的别样情愫

男人女人一起愁愁愁:现代生活中的情感共振与解忧之道

前言在上一篇文章中我们已经认识了 CAS 的核心思想、特性并通过AtomicInteger体验了 CAS 的高效性。

但你是否会有这样的疑问AtomicInteger的compareAndSet方法为什么能保证原子性Java 作为一门高级语言又是如何直接操作底层硬件来实现 CAS 的本文将带你拨开迷雾深入 Java CAS 的底层实现核心解析 Unsafe 类的作用以及 CPU 硬件如何为 CAS 提供支撑让你彻底搞懂 Java CAS 的实现链路。

CAS 是谁在“保驾护航”我们在上一篇中使用AtomicInteger实现了高效的线程安全计数器其中的核心方法compareAndSet看似简单实则背后有一个 “隐藏大佬” 在提供支撑 —— 那就是sun.misc.Unsafe类。

如果你尝试查看AtomicInteger的源码会发现它的所有 CAS 相关操作最终都委托给了 Unsafe 类的方法。

可以说Unsafe 类是 Java CAS 实现的核心依赖也是 Java 程序能够访问底层硬件指令的重要桥梁。

为什么 Java 需要通过 Unsafe 类来实现 CAS因为 CAS 是一种硬件级别的原子操作而 Java 本身是一门跨平台的高级语言屏蔽了底层硬件和操作系统的差异无法直接访问 CPU 的原子指令。

Unsafe 类就像一个 “后门”让 Java 能够突破语言层面的限制直接调用底层的 C 代码和硬件指令从而实现 CAS 这类底层操作。

接下来我们就从 Unsafe 类入手逐步拆解 Java CAS 的底层实现。

Unsafe 类的核心介绍sun.misc.Unsafe类从名字就能看出它的特殊性 ——“Unsafe”不安全这意味着它的操作具有一定的风险使用不当可能会导致 JVM 崩溃。

但同时它也是 Java 并发编程、NIO 等核心功能的底层支撑。

为何不能直接 new首先我们要明确Unsafe 类不能通过new Unsafe()的方式直接实例化这是 JDK 的刻意设计。

我们查看 Unsafe 的源码可以发现它的构造方法是私有的并且提供了一个静态的getUnsafe()方法但该方法会做权限校验只有启动类加载器Bootstrap ClassLoader加载的类才能调用普通应用类直接调用会抛出SecurityException异常。

// Unsafe类的构造方法私有无法直接实例化 private Unsafe() {} // 静态获取方法带有权限校验 CallerSensitive public static Unsafe getUnsafe() { Class? caller Reflection.getCallerClass(); // 只有启动类加载器加载的类才能通过校验 if (!VM.isSystemDomainLoader(caller.getClassLoader())) { throw new SecurityException(Unsafe); } return theUnsafe; }那么普通应用程序如果想要获取 Unsafe 实例仅用于学习和研究生产环境不推荐可以通过反射的方式获取它的私有静态变量theUnsafe代码示例如下// Unsafe类的构造方法私有无法直接实例化 private Unsafe() {} // 静态获取方法带有权限校验 CallerSensitive public static Unsafe getUnsafe() { Class? caller Reflection.getCallerClass(); // 只有启动类加载器加载的类才能通过校验 if (!VM.isSystemDomainLoader(caller.getClassLoader())) { throw new SecurityException(Unsafe); } return theUnsafe; }运行结果Unsafe实例sun.misc.Unsafe78308db

Unsafe 的核心功能Unsafe 类提供了一系列底层操作能力大致可以分为以下几类内存操作直接分配内存allocateMemory、释放内存freeMemory、修改内存值等类似于 C 语言的malloc和free不受 JVM 垃圾回收的管理使用不当容易造成内存泄漏线程调度实现线程的挂起park和唤醒unpark这是LockSupport类的底层依赖也是 AQS抽象队列同步器的核心实现基础CAS 操作提供了针对 int、long、Object 三种类型的 CAS 原子操作方法这是我们本文的核心关注点字段 / 对象操作获取对象字段的内存偏移量objectFieldOffset、静态字段的内存偏移量staticFieldOffset、直接修改对象字段值等。

CAS 相关核心方法Unsafe 类提供了三个核心的 CAS 方法分别对应三种常见的数据类型这是 Java CAS 操作的底层入口方法签名功能说明public final native boolean compareAndSwapInt(Object o, long offset, int expected, int update)对 int 类型字段执行 CAS 操作public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update)对 long 类型字段执行 CAS 操作public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update)对 Object 类型字段执行 CAS 操作这三个方法都是native方法由 C/C 实现调用底层硬件指令它们的参数含义一致o要操作的目标对象offset目标字段在对象中的内存偏移量可以理解为字段在对象内存中的 “地址偏移”通过objectFieldOffset方法获取expected字段的预期原值对应 CAS 三要素中的 Aupdate字段的目标新值对应 CAS 三要素中的 B返回值boolean类型true表示 CAS 成功false表示 CAS 失败。

CAS 的底层硬件支撑Unsafe 的 CAS 方法是 native 方法它的底层最终会调用 CPU 的原子指令。

也就是说Java CAS 的原子性本质上是由 CPU 硬件来保证的。

CAS 依赖的原子指令在 CPU 指令集中专门提供了支持 “比较并交换” 的原子指令不同的 CPU 架构实现略有差异其中最具代表性的是 x86 架构的cmpxchg指令Compare and Exchange。

cmpxchg指令的核心功能是将寄存器中的值与内存地址中的值进行比较如果相等则将寄存器中的另一个值写入该内存地址如果不相等则将内存地址中的值加载到寄存器中。

整个指令的执行过程是原子性的不会被其他线程打断。

对于多核心 CPU 来说cmpxchg指令还会配合锁机制来保证原子性主要分为总线锁和缓存锁两种。

CPU 如何保证 cmpxchg 指令的原子性在多核心 CPU 环境下每个核心都有自己的缓存内存中的数据会被加载到各个核心的缓存中。

为了保证cmpxchg指令在多核心下的原子性CPU 提供了两种锁机制

总线锁当 CPU 执行cmpxchg指令时如果操作的内存地址的数据尚未被缓存到核心中CPU 会发起一个总线锁请求。

总线锁会锁住 CPU 的前端总线FSB使得在锁生效期间其他核心无法通过总线访问内存只能等待当前核心的 CAS 操作完成后才能获取内存数据。

总线锁的优势是简单可靠能保证绝对的原子性但缺点也很明显 —— 它会阻塞其他所有核心的内存访问开销非常大严重影响多核 CPU 的并行性能。

缓存锁为了解决总线锁的性能问题CPU 引入了缓存锁基于 MESI 缓存一致性协议实现。

当 CPU 操作的内存地址的数据已经被加载到自身的缓存行中时CPU 不会发起总线锁而是通过缓存锁锁定该缓存行。

此时其他核心如果也缓存了该数据会收到缓存一致性协议的通知得知该缓存行已被锁定无法对其进行修改只能等待锁释放后重新从内存中加载最新数据。

缓存锁的优势是开销远小于总线锁只锁定当前核心的缓存行不影响其他核心对其他内存地址的访问能充分发挥多核 CPU 的并行性能缺点是仅对缓存行中的数据有效对于未缓存的数据或超出缓存行大小的数据仍会退化为总线锁。

不同架构的差异除了 x86 架构ARM 架构广泛应用于移动端、服务器端也提供了 CAS 对应的原子指令例如ldrex加载并标记独占和strex存储并检查独占的组合指令ldrex从内存中加载数据到寄存器并标记该内存地址为 “独占访问”strex尝试将寄存器中的新值写入该内存地址如果该地址仍保持 “独占访问” 标记未被其他核心修改则写入成功否则写入失败。

这组指令的组合效果与 x86 架构的cmpxchg指令一致都能实现原子的 “比较并交换” 操作。

不同架构的 CAS 指令实现细节不同但核心功能一致这也是 Java CAS 能够跨平台使用的基础。

AtomicInteger 的 CAS 实现逻辑了解了 Unsafe 类和 CPU 硬件支撑后我们再回头看AtomicInteger的源码就能清晰地梳理出它的 CAS 实现链路。

AtomicInteger 的 value 属性volatile 修饰的意义首先查看AtomicInteger的核心属性valuepublic class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID 6214790243416807050L; // 获取Unsafe实例AtomicInteger由启动类加载器加载可直接调用getUnsafe() private static final Unsafe unsafe Unsafe.getUnsafe(); // value字段的内存偏移量静态代码块中初始化 private static final long valueOffset; static { try { // 获取value字段在AtomicInteger对象中的内存偏移量 valueOffset unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField(value)); } catch (Exception ex) { throw new Error(ex); } } // 核心属性volatile修饰的int类型变量 private volatile int value; // 构造方法初始化value值 public AtomicInteger(int initialValue) { value initialValue; } public AtomicInteger() { } // 其他方法省略... }这里有一个关键细节value属性被volatile关键字修饰。

这的作用是保证 value 变量的可见性当一个线程修改了 value 的值后其他线程能够立即看到修改后的最新值而不是读取到自己缓存中的旧值。

需要注意的是volatile只能保证可见性和禁止指令重排序无法保证原子性。

而AtomicInteger的原子性是由 Unsafe 的 CAS 方法保证的volatile在这里是辅助作用两者结合才实现了AtomicInteger的线程安全。

compareAndSet 方法源码追踪接下来我们追踪AtomicInteger的compareAndSet方法源码// AtomicInteger的compareAndSet方法 public final boolean compareAndSet(int expect, int update) { // 直接调用Unsafe的compareAndSwapInt方法 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }可以看到AtomicInteger的compareAndSet方法非常简洁没有任何复杂逻辑直接委托给了 Unsafe 的compareAndSwapInt方法。

其中三个核心参数的来源this当前AtomicInteger对象对应 Unsafe 方法中的目标对象 ovalueOffsetvalue字段的内存偏移量在静态代码块中通过unsafe.objectFieldOffset获取expect和update用户传入的预期值和新值对应 CAS 三要素中的 A 和 B。

除此之外AtomicInteger的常用方法如incrementAndGet、getAndIncrement底层也都是基于 Unsafe 的compareAndSwapInt实现的我们以incrementAndGet为例// AtomicInteger的原子自增并返回新值方法 public final int incrementAndGet() { // getAndAddInt方法原子性地将value增加1并返回旧值再通过旧值1得到新值 return unsafe.getAndAddInt(this, valueOffset,

1; } // Unsafe的getAndAddInt方法 public final int getAndAddInt(Object o, long offset, int delta) { int v; // 自旋CAS如果CAS失败则不断重试直到CAS成功 do { // 获取当前value的最新值volatile保证可见性 v this.getIntVolatile(o, offset); // 尝试CAS更新预期值为v新值为vdelta } while(!this.compareAndSwapInt(o, offset, v, v delta)); // 返回更新前的旧值 return v; }从源码中可以发现incrementAndGet底层依赖了 Unsafe 的getAndAddInt方法而getAndAddInt方法内部使用了自旋 CAS通过do-while循环不断尝试执行 CAS 操作如果失败则重新获取最新的 value 值再次尝试直到 CAS 成功为止。

这也是无锁编程中处理 CAS 失败的常用策略。

Unsafe.compareAndSwapInt 的参数解析我们再对 Unsafe 的compareAndSwapInt方法的参数进行详细解析确保大家彻底理解public final native boolean compareAndSwapInt(Object o, long offset, int expected, int update);Object o要操作的目标对象。

比如AtomicInteger中就是AtomicInteger实例本身通过它可以定位到该对象在内存中的起始地址long offset目标字段的内存偏移量。

它是相对于目标对象起始地址的偏移值通过o offset就能精确计算出目标字段如AtomicInteger的value在内存中的绝对地址对应 CAS 三要素中的内存地址 Vint expected线程预期的字段原值对应 CAS 三要素中的 Aint update线程想要更新的字段新值对应 CAS 三要素中的 B。

整个方法的执行逻辑通过o offset找到内存地址 V比较 V 的实际值与expectedA是否一致一致则将 V 的值更新为updateB返回 true不一致则不执行更新返回 false。

实践直接使用 Unsafe 实现 CAS 操作谨慎使用的提醒虽然生产环境中我们很少直接使用 Unsafe 类推荐使用 JDK 提供的AtomicXxx原子类但为了加深理解我们可以通过 Unsafe 手动实现一个简单的 CAS 操作操作自定义对象的字段。

代码示例通过 Unsafe 操作自定义对象的属性import sun.misc.Unsafe; import java.lang.reflect.Field; /** * 直接使用Unsafe实现CAS操作 */ public class UnsafeCASDemo { // 自定义实体类 static class User { // 要操作的字段volatile修饰保证可见性 private volatile String name; private int age; public User(String name, int age) { this.name name; this.age age; } public String getName() { return name; } public int getAge() { return age; } Override public String toString() { return User{name name , age age }; } } public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { //

通过反射获取Unsafe实例 Field theUnsafeField Unsafe.class.getDeclaredField(theUnsafe); theUnsafeField.setAccessible(true); Unsafe unsafe (Unsafe) theUnsafeField.get(null); //

创建User对象 User user new User(张三,

; System.out.println(初始User对象 user); //

获取User类中name字段的内存偏移量 Field nameField User.class.getDeclaredField(name); long nameOffset unsafe.objectFieldOffset(nameField); //

使用Unsafe执行CAS操作将name从张三改为李四 boolean casSuccess unsafe.compareAndSwapObject(user, nameOffset, 张三, 李

; System.out.println(CAS操作是否成功 casSuccess); System.out.println(CAS操作后User对象 user); //

再次执行CAS操作预期值张三与实际值李四不一致操作失败 boolean casFail unsafe.compareAndSwapObject(user, nameOffset, 张三, 王

; System.out.println(第二次CAS操作是否成功 casFail); System.out.println(第二次CAS操作后User对象 user); } }运行结果初始User对象User{name张三, age20} CAS操作是否成功true CAS操作后User对象User{name李四, age20} 第二次CAS操作是否成功false 第二次CAS操作后User对象User{name李四, age20}从运行结果可以看到我们成功通过 Unsafe 的compareAndSwapObject方法实现了对User对象name字段的 CAS 操作验证了 Unsafe CAS 方法的有效性。

风险提示Unsafe 操作的潜在危害在这里必须强调生产环境中应尽量避免直接使用 Unsafe 类它的操作存在诸多风险内存操作风险Unsafe 的内存分配和释放不受 JVM 垃圾回收管理使用不当容易造成内存泄漏、内存溢出甚至破坏 JVM 的内存结构导致 JVM 崩溃线程安全风险Unsafe 的操作绕过了 Java 的访问权限控制可以修改私有字段如果没有充分考虑线程安全容易引入难以排查的并发问题跨平台兼容性风险Unsafe 的部分方法依赖特定的 CPU 架构和操作系统在不同平台上的表现可能不一致破坏了 Java 的跨平台特性API 不稳定性风险Unsafe 类是 sun.misc 包下的内部 API并非 JDK 的标准公共 API未来 JDK 版本可能会对其进行修改、废弃导致基于它的代码无法兼容新版本 JDK。

JDK 提供了java.util.concurrent.atomic包下的原子类AtomicInteger、AtomicReference等这些类已经封装了 Unsafe 的 CAS 操作并且经过了严格的测试和优化是我们在生产环境中的首选。

七、

总结本文我们深入剖析了 Java CAS 的底层实现核心知识点可以

总结为以下几点核心依赖Java CAS 的底层依赖sun.misc.Unsafe类它是 Java 访问底层硬件指令的 “后门”Unsafe 关键信息Unsafe 不能直接 new可通过反射获取它提供了compareAndSwapInt/Long/Object三个核心 CAS 方法均为 native 方法硬件支撑Java CAS 的原子性由 CPU 硬件保证x86 架构依赖cmpxchg指令通过总线锁低性能和缓存锁高性能保证指令原子性ARM 架构依赖ldrexstrex组合指令实现链路AtomicInteger应用层→ compareAndSet方法 → Unsafe 的compareAndSwapInt底层工具 → CPU 原子指令cmpxchg硬件层关键细节AtomicInteger的value字段被volatile修饰保证可见性incrementAndGet等方法底层使用自旋 CAS失败后自动重试使用建议生产环境优先使用 JDK 提供的AtomicXxx原子类避免直接使用 Unsafe 类防止引入内存风险和兼容性问题。

通过本文的学习你已经掌握了 Java CAS 的底层实现逻辑接下来我们将探讨 CAS 存在的三大核心问题及其解决方案带你更全面地认识 CAS 的适用场景和局限性

高清无码-高清无码应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123