Java-01-源码篇-JUC并发编程-原子类
在J.U.C并发包之中,有一个原子包(java.util.concurrent.atomic)该包里面的类都天生拥有原子性质。其原子性质的表现在于多线程并发的环境下统计计算和赋值业务自带有锁功能,从而自带有原子性。
该原子包(java.util.concurrent.atomic)下提供了一系列的原子类型(有基本数据类型的原子类,引用类型的原子类,数组类型的原子类,原子类型的属性修改器)
java.util.current.atomic 包中提供了多种原子性的操作类支持,这些操作类可以分为四类:
Ø 基本类型:Atomicinteger, AtomicLong, AtomicBoolean
Ø 数组类型:AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
Ø 引用类型: AtomicReference, AtomicStampedReference, AtomicMarkableReference
Ø 对象的属性修改类型:
AtomicIntegerFieldUpdater,
AtomicLongFieldUpater,
AtomicReferenceFieldUpdater
原则:所有的子类都具有同步的支持,但是考虑到性能的问题,没有使用到synchronized关健字来实现,使依赖底层完成的
一,基本类型的原子操作
基本类型的原子类有:AtomicInteger, AtomicLong, AtomicBoolean
以Atomicinteger的统计案例为例
class AtomicCounter {private AtomicInteger count = new AtomicInteger(0);// 现在是线程安全的public int increment() {return count.getAndIncrement(); // 累计加一,addAndGet(delta) 方法是累计加指定数量}public int getCount() {return count.get();}
}public class AtomicCounterExample {public static void main(String[] args) throws InterruptedException {AtomicCounter counter = new AtomicCounter();Thread t1 = new Thread(() -> {IntStream.range(0, 10000).forEach(i -> counter.increment());System.out.println("T1执行完毕");});Thread t2 = new Thread(() -> {IntStream.range(0, 10000).forEach(i -> counter.increment());System.out.println("T2执行完毕");});t1.start();t2.start();t1.join();t2.join();System.out.println("最终 count 值:" + counter.getCount()); // 结果一定是 20000}
}
输出结果:
T1执行完毕
T2执行完毕
最终 count 值:20000
从上面的案例中可以得知,AtomicCounter 类的increment累计方法并没有上锁,而是直接通过Java J.U.C提供的原子类 AtomicInteger 的累计方法
count.getAndIncrement();
该方法里面本身就提供的锁机制。而锁的实现并非是通过JVM的内置锁(synchronized),而是通过CAS来实现锁的机制。
观察原子类AtomicInteger的累计方法
private static final Unsafe U = Unsafe.getUnsafe();public final int getAndIncrement() {return U.getAndAddInt(this, VALUE, 1);}
继续观察 Unsafe类的 getAndAddInt()方法
@IntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {int v;do {v = getIntVolatile(o, offset);} while (!weakCompareAndSetInt(o, offset, v, v + delta));return v;
}@IntrinsicCandidate
public final boolean weakCompareAndSetInt(Object o, long offset, int expected, int x) {return compareAndSetInt(o, offset, expected, x);
}// 看到 native 关键字就知道是调用的是本地方法。
@IntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);
看到 compareAndSetInt方法就知道是Java提供的CAS实现方式。
这样的基本类型的原子类除了有int类型以外还有Boolean, Long等基本类型的修改
public class AtomicLong extends Number implements java.io.Serializable {}
public class AtomicInteger extends Number implements java.io.Serializable {}
public class AtomicBoolean implements java.io.Serializable {}
除了AtomicBoolean继承Object,AtomicLong,AtomicInteger类型还是属于数字类型的对象,只不过这些数字类型在积累计算天生有着原子性操作。这样的继承结构也符合面向对象的思想。
二,数组类型的原子操作
数组类型的原子的有:AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray。前面两个从名称可以得知,一个是修改int数组的,一个是修改long数组,而第三个是修改对象数组的 Reference 是 “引用”的意思。
2.1使用 AtomicReferenceArray
管理商品库存状态案例
管理商品库存状态,状态可以是: "IN_STOCK"
(有货),"OUT_OF_STOCK"
(售罄)
public class AtomicReferenceArrayStockManager {// 模拟 5 个商品的库存状态,初始时全部为 "IN_STOCK"private static AtomicReferenceArray<String> stockStatus =new AtomicReferenceArray<>(new String[]{"IN_STOCK", "IN_STOCK", "IN_STOCK", "IN_STOCK", "IN_STOCK"});public static void main(String[] args) throws InterruptedException {// 创建 10 个线程模拟 10 个用户同时抢购商品Thread[] threads = new Thread[10];for (int i = 0; i < threads.length; i++) {final int userId = i;threads[i] = new Thread(() -> attemptPurchase(userId), "User-" + userId);threads[i].start();}// 等待所有线程执行完毕for (Thread thread : threads) thread.join();// 最终库存状态System.out.println("最终库存状态:");for (int i = 0; i < stockStatus.length(); i++) {System.out.println("商品 " + i + " 状态: " + stockStatus.get(i));}}// 模拟用户购买商品private static void attemptPurchase(int userId) {for (int i = 0; i < stockStatus.length(); i++) {// 尝试将商品状态从 "IN_STOCK" 变为 "OUT_OF_STOCK"boolean success = stockStatus.compareAndSet(i, "IN_STOCK", "OUT_OF_STOCK");if (success) {System.out.println(Thread.currentThread().getName() + " 成功购买了商品 " + i);return; // 购买成功后退出}}System.out.println(Thread.currentThread().getName() + " 抢购失败,所有商品已售罄");}
}
输出结果:
User-6 抢购失败,所有商品已售罄
User-9 抢购失败,所有商品已售罄
User-7 抢购失败,所有商品已售罄
User-8 抢购失败,所有商品已售罄
User-4 成功购买了商品 4
User-5 抢购失败,所有商品已售罄
User-0 成功购买了商品 0
User-2 成功购买了商品 1
User-3 成功购买了商品 3
User-1 成功购买了商品 2
最终库存状态:
商品 0 状态: OUT_OF_STOCK
商品 1 状态: OUT_OF_STOCK
商品 2 状态: OUT_OF_STOCK
商品 3 状态: OUT_OF_STOCK
商品 4 状态: OUT_OF_STOCK
从输出结果当中可以得知,在多线程并发的环境下只有0 ~ 4号用户抢到,其他用户并没有抢到。保证某个商品不会被重复售卖
三,Unsafe类是什么?
Unsafe
是 Java 提供的 低级别内存操作 API,主要用于高性能并发编程。 Unsafe
类属于 sun.misc.Unsafe
,是 Java 低级 API,提供:
- 直接操作 内存(allocate/free)
- CAS 操作(compare-and-swap)
- 线程调度(park/unpark)
- 字段偏移(field offsets)
- 对象实例化(无构造函数)
🔴 ⚠️ Unsafe
非官方 API,使用风险较高,不安全! 🔴
3.1 直接操作内存案例
public class UnsafeExample {public static void main(String[] args) throws Exception {// 1️⃣ 反射获取 Unsafe 实例Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);Unsafe unsafe = (Unsafe) field.get(null);// 2️⃣ 直接分配内存(不受 JVM GC 管理)long memory = unsafe.allocateMemory(8); // 分配 8 字节unsafe.putLong(memory, 123456789L);System.out.println("内存中的值: " + unsafe.getLong(memory));// 3️⃣ 释放内存unsafe.freeMemory(memory);}
}
✅ 可以操作底层内存,不受 GC 影响
❌ 可能导致 JVM 崩溃
3.2 跳转JVM内存管理机制案例
class Example {private String message;private Example() { // 私有构造方法this.message = "Constructor Invoked!";System.out.println("Example 构造方法执行!");}public String getMessage() {return message;}@Overridepublic String toString() {return "Example{" +"message='" + message + '\'' +'}';}
}public class UnsafeInstanceExample {public static void main(String[] args) throws Exception {// 1️⃣ 反射获取 Unsafe 实例Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);Unsafe unsafe = (Unsafe) theUnsafe.get(null);// 2️⃣ 直接分配对象内存,不调用构造方法Example instance = (Example) unsafe.allocateInstance(Example.class);// 3️⃣ 查看对象状态(不会调用构造方法,所以 message = null)System.out.println("对象实例化成功: " + instance);System.out.println("message: " + instance.getMessage()); // null}
}
输出结果:跳过JVM管理机制,直接进行对象实例化,但并没有调用构造方法。这样就跳过JVM的管理机制,因为如果是JVM来管理的对象的话,实例化过程,构造对象肯定会被调用
对象实例化成功: Example{message='null'}
message: null
3.3 修改私有字段
public class UnsafeFieldExample {private String secret = "original value";public static void main(String[] args) throws Exception {Unsafe unsafe = Unsafe.getUnsafe();UnsafeFieldExample obj = new UnsafeFieldExample();Field field = UnsafeFieldExample.class.getDeclaredField("secret");// 获取字段的内存偏移量long offset = unsafe.objectFieldOffset(field);// 修改私有字段的值unsafe.putObject(obj, offset, "new secret value");System.out.println("修改后的值: " + obj.secret); // 输出:"new secret value"}
}
//
如果是JDK9以上是不可性的,会报错:Exception in thread "main" java.lang.SecurityException: Unsafe
原因是在 JDK 9+(包括 JDK 17),Unsafe.getUnsafe()
默认禁止直接访问,因为它带来了 严重的安全风险。在 JDK 8 及之前,可以直接调用 Unsafe.getUnsafe()
,但在 JDK 9+,你会遇到
Exception in thread "main" java.lang.SecurityException: Unsafeat jdk.unsupported/sun.misc.Unsafe.getUnsafe(Unsafe.java:99)at com.toast.javase.source.unfase.UnsafeFieldExample.main(UnsafeFieldExample.java:16)
因此JDK9以上的写法,需要通过反射来实现
public class UnsafeFieldExample {private String secret = "original value";public static void main(String[] args) throws Exception {// 通过反射获取 Unsafe 实例Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");unsafeField.setAccessible(true);Unsafe unsafe = (Unsafe) unsafeField.get(null);UnsafeFieldExample obj = new UnsafeFieldExample();Field field = UnsafeFieldExample.class.getDeclaredField("secret");// 获取字段的内存偏移量long offset = unsafe.objectFieldOffset(field);// 修改私有字段的值unsafe.putObject(obj, offset, "new secret value");System.out.println("修改后的值: " + obj.secret); // 输出:"new secret value"}
}
输出结果
修改后的值: new secret value
四,CAS
4.1 什么是CAS
从锁的思想上分类,锁有乐观锁和悲观锁,而CAS属于乐观锁,synchronized 关键字属于悲观锁。
从计算机的角度来讲,CAS(Compare-And-Swap) 是属于 CPU 级别的原子指令,Unsafe
通过 compareAndSwapXXX
方法封装了它,用于高性能并发编程。 属于 无锁并发编程。它的本质是:
如果变量的当前值等于期望值,则更新为新值;否则,不做任何修改。
CAS 需要 三个操作数:
V(Variable):变量的当前值
E(Expected Value):期望值
N(New Value):需要更新的新值
操作流程:
比较 变量当前值 V
是否等于 E
(期望值)
如果相等 → 说明没有其他线程修改 V
,将 V
更新为 N
,返回 成功。
如果不相等 → 说明 V
已被其他线程修改,放弃更新,返回 失败。
CAS 的核心思想:乐观锁(Optimistic Locking),假设大部分情况下数据不会被修改,只有在真正修改时才检查是否冲突。
4.2 CAS 的 CPU 指令支持
CPU 指令级别: CAS 在 x86 架构 中通常由 cmpxchg
指令实现,工作原理如下:
- 读取变量的当前值。
- 比较当前值和期望值:
-
- ✅ 如果相等,写入新值(修改成功)。
- ❌ 如果不相等,不做修改(修改失败)。
- 返回是否成功。
mov eax, [memory] ; 读取变量值到 eax
cmpxchg [memory], ecx ; 如果 eax == memory,则 memory = ecx,否则 eax = memory
4.3 JVM层面对CAS的支持
JVM 提供了 CAS 指令的封装,主要通过 Unsafe
类和 VarHandle
来实现原子性操作。
JVM 内部的 CAS 操作
在 HotSpot JVM 中,CAS 操作最终调用的是 Unsafe.compareAndSwapXXX
:
boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
JVM 会将 compareAndSwapInt
直接映射 到 CPU 的 cmpxchg
指令,避免上下文切换,提高性能。
4.4 小总结
Unsafe的 compareAndSet() 是数据修改操作方法在J.U.C中被称为CAS机制。
CAS(Compare-And-Swap)是一条CPU并发原语( 并发原语是CPU的 原生指令cmpxchg
,是直接写在CPU内部的程序代码 )。它的功能是判断内存某一个位置的值是否为预期值,如果是则更改为新的值。反之则不进行修改,这个过程属于原子性操作
在多线程进行数据修改时,为了保证数据修改的正确性,常规的做法就是使用synchronized 同步锁,但是这种锁属于“悲观锁(Pessimistic Lock)”每一个线程都需要在操作之前锁定当前的内存区域,而后才可以进行处理,这样一来在高并发环境下就会严重影响到程序的处理性能
而CAS采用的是一种“乐观锁(Optimistic Lock)机制”,其最大的操作特点就是不强制性的同步处理(无synchronized),而为了保证数据修改的正确性,添加一些比较的数据(列如:compareAndSet()在修改之前需要进行数据的比较),采用的是一种冲突重试的处理机制,这样可以有效的避免线程阻塞问题的出现,在并发竞争不是很激烈的情况下,可以获得较好的处理性能,而在JDK9后为了进一步提升CAS的操作性能,又追加了硬件处理指定集的支持,可以充分的发挥服务器硬件配置的优势,得到更好得处理性能
五,系列文章推荐
最后,如果这篇文章对你有帮助,欢迎 点赞👍、收藏📌、关注👀!
我会持续分享 Java、Spring Boot、MyBatis-Plus、微服务架构 相关的实战经验,记得关注,第一时间获取最新文章!🚀
这篇文章是 【Java SE 17源码】系列 的一部分,详细地址:
java SE 17 源码篇_吐司呐的博客-CSDN博客
记得 关注我,后续还会更新更多高质量技术文章!
你在实际开发中遇到过类似的问题吗?
欢迎在评论区留言交流,一起探讨 Java 开发的最佳实践! 🚀