在Java并发编程领域,Unsafe类作为底层操作的核心入口,提供了很多直接操作内存和线程的能力,其中park和unpark方法常被用于实现线程的阻塞与唤醒,是构建自定义轻量级同步组件的重要基础。这两个方法相比传统的Object.wait和Object.notify机制,使用更加灵活,性能也更有优势,适合用来实现一些特定场景下的同步工具。

Unsafe中park与unpark的基本用法
Unsafe类属于sun.misc包,正常情况下无法直接实例化,需要通过反射获取其实例。park方法用于阻塞当前线程,unpark方法用于唤醒指定线程。两者的核心特性是unpark可以先于park调用,此时线程后续调用park会立即返回,不会出现线程永久阻塞的问题。
获取Unsafe实例的代码如下:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeUtil {
private static final Unsafe UNSAFE;
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Unsafe getUnsafe() {
return UNSAFE;
}
}
park方法有两个重载版本,一个是无参的park(),阻塞当前线程直到被unpark唤醒、中断或者虚假唤醒;另一个是park(Object blocker),可以传入一个阻塞对象,方便排查线程阻塞问题。unpark方法接收一个Thread类型的参数,用于指定要唤醒的线程。
构建自定义轻量级同步组件的核心思路
要基于park和unpark构建同步组件,核心需要解决三个问题:线程状态的记录、阻塞线程的管理、以及同步状态的原子更新。我们可以参考AQS的设计思路,但去掉复杂的队列管理,实现更轻量的版本。
核心设计点
- 使用volatile变量记录同步状态,比如用一个int类型的state表示锁的占用情况,0表示未占用,1表示已占用
- 使用CAS操作更新同步状态,保证状态修改的原子性
- 当线程获取同步状态失败时,调用park方法阻塞当前线程,并将线程信息记录下来
- 当持有同步状态的线程释放资源时,调用unpark方法唤醒等待的线程
自定义轻量级锁的完整实现示例
下面我们实现一个简单的非重入轻量级锁,仅支持独占模式,适合单线程短时间占用资源的场景,相比synchronized更加轻量,没有monitor的额外开销。
import sun.misc.Unsafe;
import java.util.concurrent.locks.LockSupport;
public class LightweightLock {
// 同步状态 0:未锁定 1:已锁定
private volatile int state = 0;
// 记录当前持有锁的线程
private Thread ownerThread = null;
// 等待线程的标记,这里简化实现,仅记录一个等待线程
private volatile Thread waitThread = null;
// 获取Unsafe实例
private static final Unsafe UNSAFE = UnsafeUtil.getUnsafe();
// state字段的偏移量,用于CAS操作
private static final long stateOffset;
// waitThread字段的偏移量
private static final long waitThreadOffset;
static {
try {
stateOffset = UNSAFE.objectFieldOffset(LightweightLock.class.getDeclaredField("state"));
waitThreadOffset = UNSAFE.objectFieldOffset(LightweightLock.class.getDeclaredField("waitThread"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 获取锁
public void lock() {
// 尝试CAS将state从0改为1
if (UNSAFE.compareAndSwapInt(this, stateOffset, 0, 1)) {
// CAS成功,设置持有线程
ownerThread = Thread.currentThread();
return;
}
// CAS失败,当前线程需要阻塞等待
Thread currentThread = Thread.currentThread();
// 将当前线程设置为等待线程
UNSAFE.compareAndSwapObject(this, waitThreadOffset, null, currentThread);
// 再次尝试获取锁,避免设置等待线程后锁刚好被释放的情况
if (UNSAFE.compareAndSwapInt(this, stateOffset, 0, 1)) {
ownerThread = currentThread;
// 清除等待线程标记
UNSAFE.compareAndSwapObject(this, waitThreadOffset, currentThread, null);
return;
}
// 阻塞当前线程
LockSupport.park(this);
}
// 释放锁
public void unlock() {
// 检查是否是持有锁的线程调用
if (ownerThread != Thread.currentThread()) {
throw new IllegalMonitorStateException("当前线程未持有锁");
}
// 重置持有线程
ownerThread = null;
// 将state改回0
state = 0;
// 获取等待线程
Thread wt = waitThread;
if (wt != null) {
// 清除等待线程标记
UNSAFE.compareAndSwapObject(this, waitThreadOffset, wt, null);
// 唤醒等待线程
LockSupport.unpark(wt);
}
}
}
上面的实现中,我们使用了Unsafe的CAS操作来更新state和waitThread字段,避免了使用synchronized带来的额外开销。LockSupport的park和unpark底层就是调用Unsafe的park和unpark方法,使用起来更加方便。
使用示例与测试
下面通过一个简单的多线程测试来验证这个轻量级锁的正确性:
public class LightweightLockTest {
private static int count = 0;
private static final LightweightLock lock = new LightweightLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终count值:" + count);
}
}
运行上述代码,最终输出的count值会是20000,说明这个轻量级锁可以正确保证并发场景下的原子性。
注意事项与优化方向
在实际使用基于Unsafe的park和unpark构建同步组件时,需要注意以下几点:
- Unsafe类属于内部API,不同JDK版本的实现可能有差异,生产环境使用前需要确认兼容性
- 上述示例是简化版本,仅支持单个等待线程,实际场景中需要维护一个等待队列来管理多个阻塞线程
- 需要处理虚假唤醒的问题,park方法可能在没有被unpark的情况下返回,所以阻塞后需要重新检查同步状态
- 如果需要支持可重入、共享模式等特性,需要额外扩展状态管理和线程记录的逻辑
这种自定义轻量级同步组件适合在锁竞争不激烈、锁占用时间极短的场景下使用,相比传统的synchronized和ReentrantLock,能减少额外的队列管理和状态检查开销,获得更好的性能表现。