Unsafe类与CAS操作
约 2114 字大约 7 分钟
javaunsafecas
2025-03-20
概述
sun.misc.Unsafe(JDK 9+ 为 jdk.internal.misc.Unsafe)是Java中最"危险"也最强大的底层工具类。它提供了直接内存操作、CAS原子操作、线程调度等低级功能,是 java.util.concurrent 包的基石。虽然不建议在业务代码中直接使用,但理解Unsafe对于深入学习JUC并发工具至关重要。
获取Unsafe实例
// Unsafe的构造方法是private的,且getUnsafe()会检查调用者是否是Bootstrap ClassLoader加载的
// 常规方式会抛出SecurityException
// 方式1:反射获取(常用于学习和测试)
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
// 方式2:JVM参数 -Xbootclasspath 将自己的类加入引导类路径(不推荐)
// 方式3:JDK 9+ VarHandle提供了部分Unsafe功能的安全替代CAS操作(Compare And Swap)
CAS是乐观锁的核心原语,由CPU指令直接支持(x86的 cmpxchg 指令),是无锁并发的基础。
Unsafe中的CAS方法
public final class Unsafe {
// 比较并交换Object的字段
public final native boolean compareAndSwapObject(
Object obj, long offset, Object expected, Object update);
// 比较并交换int字段
public final native boolean compareAndSwapInt(
Object obj, long offset, int expected, int update);
// 比较并交换long字段
public final native boolean compareAndSwapLong(
Object obj, long offset, long expected, long update);
// 获取字段的内存偏移量
public native long objectFieldOffset(Field f);
}CAS使用示例
public class CasCounter {
private volatile int count = 0;
private static final Unsafe unsafe;
private static final long countOffset;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
countOffset = unsafe.objectFieldOffset(
CasCounter.class.getDeclaredField("count"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void increment() {
int current;
do {
current = unsafe.getIntVolatile(this, countOffset);
} while (!unsafe.compareAndSwapInt(this, countOffset, current, current + 1));
// 自旋CAS:失败则重试
}
public int getCount() {
return count;
}
}CAS在JUC中的应用
// AtomicInteger的incrementAndGet实现
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// getAndAddInt内部(自旋CAS)
public final int getAndAddInt(Object obj, long offset, int delta) {
int v;
do {
v = getIntVolatile(obj, offset);
} while (!compareAndSwapInt(obj, offset, v, v + delta));
return v;
}CAS的ABA问题
ABA问题:值从A变为B再变回A,CAS认为没有变化,但中间状态可能影响业务逻辑。
解决方案:带版本号的CAS
// AtomicStampedReference — 带"邮戳"的原子引用
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
// 获取当前值和版本号
int[] stampHolder = new int[1];
String current = ref.get(stampHolder);
int stamp = stampHolder[0];
// CAS时同时比较值和版本号
boolean success = ref.compareAndSet(
"A", // 期望值
"B", // 新值
stamp, // 期望版本号
stamp + 1 // 新版本号
);
// AtomicMarkableReference — 带boolean标记的原子引用
AtomicMarkableReference<String> markRef = new AtomicMarkableReference<>("A", false);内存操作
直接内存分配
// 分配堆外内存(不受GC管理)
long address = unsafe.allocateMemory(1024); // 分配1024字节
unsafe.setMemory(address, 1024, (byte) 0); // 初始化为0
// 写入数据
unsafe.putByte(address, (byte) 65);
unsafe.putInt(address + 1, 42);
unsafe.putLong(address + 5, 123456789L);
// 读取数据
byte b = unsafe.getByte(address); // 65
int i = unsafe.getInt(address + 1); // 42
long l = unsafe.getLong(address + 5); // 123456789
// 重新分配内存
address = unsafe.reallocateMemory(address, 2048);
// 释放内存(必须手动释放,否则内存泄漏)
unsafe.freeMemory(address);DirectByteBuffer 底层就使用 Unsafe.allocateMemory() 分配堆外内存。
内存拷贝
// 内存间拷贝
unsafe.copyMemory(srcAddress, destAddress, byteCount);
// 对象到内存
unsafe.copyMemory(srcObject, srcOffset, null, destAddress, byteCount);字段偏移量与直接字段访问
public class User {
private String name;
private int age;
}
// 获取字段偏移量
long nameOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("name"));
long ageOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("age"));
User user = new User();
// 直接写入字段(绕过访问控制)
unsafe.putObject(user, nameOffset, "Alice");
unsafe.putInt(user, ageOffset, 25);
// 直接读取字段
String name = (String) unsafe.getObject(user, nameOffset); // "Alice"
int age = unsafe.getInt(user, ageOffset); // 25
// volatile语义的读写
unsafe.putObjectVolatile(user, nameOffset, "Bob");
String volatileName = (String) unsafe.getObjectVolatile(user, nameOffset);
// putOrdered(lazySet,StoreStore屏障,不保证立即可见但有序)
unsafe.putOrderedObject(user, nameOffset, "Charlie");
unsafe.putOrderedInt(user, ageOffset, 30);数组操作
int[] array = new int[10];
// 获取数组元素的基础偏移和缩放因子
int baseOffset = unsafe.arrayBaseOffset(int[].class); // 数组首元素偏移
int indexScale = unsafe.arrayIndexScale(int[].class); // 每个元素的大小(4字节)
// 直接操作数组元素
long elementOffset = baseOffset + (long) 3 * indexScale; // array[3]的偏移
unsafe.putInt(array, elementOffset, 42); // array[3] = 42
int value = unsafe.getInt(array, elementOffset); // value = 42
// CAS操作数组元素
unsafe.compareAndSwapInt(array, elementOffset, 42, 100); // array[3]: 42 → 100AtomicIntegerArray 就是基于此实现的原子数组操作。
线程调度
park / unpark
// 阻塞当前线程(类似wait,但不需要Monitor)
unsafe.park(false, 0L); // 无限期阻塞
unsafe.park(false, 1000000000L); // 阻塞最多1秒(纳秒)
unsafe.park(true, System.currentTimeMillis() + 1000); // 阻塞到指定时间点
// 唤醒指定线程(可以先于park调用,形成"许可")
unsafe.unpark(targetThread);LockSupport.park() / LockSupport.unpark() 就是对 Unsafe.park() / Unsafe.unpark() 的封装。
park/unpark 相比 wait/notify 的优势:
- 不需要在synchronized块中调用
- unpark可以先于park调用(许可机制)
- 可以精确唤醒指定线程
对象实例化
// 绕过构造方法创建对象实例
User user = (User) unsafe.allocateInstance(User.class);
// 对象的字段是默认零值(name=null, age=0)
// 构造方法不会被调用
// 应用场景:反序列化框架(如Objenesis、Kryo)JDK 9+ 的替代方案:VarHandle
JDK 9 引入了 VarHandle 作为 Unsafe CAS操作的安全替代。
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class VarHandleCounter {
private volatile int count = 0;
private static final VarHandle COUNT_HANDLE;
static {
try {
COUNT_HANDLE = MethodHandles.lookup()
.findVarHandle(VarHandleCounter.class, "count", int.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void increment() {
int current;
do {
current = (int) COUNT_HANDLE.getVolatile(this);
} while (!COUNT_HANDLE.compareAndSet(this, current, current + 1));
}
// 更简洁的方式
public void incrementSimple() {
COUNT_HANDLE.getAndAdd(this, 1);
}
}| Unsafe方法 | VarHandle替代 |
|---|---|
compareAndSwapInt | compareAndSet |
getIntVolatile | getVolatile |
putIntVolatile | setVolatile |
putOrderedInt | setRelease |
getAndAddInt | getAndAdd |
Unsafe在JUC中的应用全景
常见FAQ
Q: 为什么Unsafe被称为"Unsafe"?
A: 因为它可以绕过Java的内存安全机制:直接操作内存地址、绕过访问控制读写字段、创建不经构造方法的对象。使用不当会导致JVM崩溃、内存泄漏、数据损坏等严重问题。
Q: JDK未来会移除Unsafe吗?
A: JDK一直在逐步提供安全的替代方案:VarHandle(JDK 9)替代CAS操作,MemorySegment/MemoryLayout(JDK 22 Foreign Function & Memory API)替代直接内存操作。sun.misc.Unsafe 已被标记为不推荐使用,但由于大量库依赖它,短期内不会完全移除。
Q: AtomicInteger和synchronized哪个更快?
A: 低竞争下AtomicInteger(CAS)更快,因为没有上下文切换开销。高竞争下,CAS自旋空转浪费CPU,synchronized的阻塞策略可能更合适。JDK 8的LongAdder通过分段CAS在高竞争下表现优于AtomicLong。
Q: CAS一定比锁好吗?
A: 不一定。CAS适合竞争不激烈、操作简单的场景。如果CAS竞争激烈导致大量自旋,或者需要保护的操作逻辑复杂(多步骤),锁是更好的选择。
总结
Unsafe 类是Java并发基础设施的底层引擎,提供了CAS、直接内存操作、线程调度等核心能力。JUC中的原子类、AQS、ConcurrentHashMap等都依赖它实现无锁并发。虽然业务代码不应直接使用Unsafe,但理解其原理对于深入掌握Java并发编程至关重要。随着VarHandle和Foreign Memory API的成熟,Java正在以更安全的方式暴露这些底层能力。
贡献者
更新日志
9f6c2-feat: organize wiki content and refresh site setup于