park和unpark机制详解
是用来创建锁和其他同步类的线程原语位置作用🔧 提供更灵活的线程阻塞和唤醒机制🔧 是构建其他并发工具的基础(如AQS)🔧 比wait/notify更强大和易用// 尝试获取锁if (!// 获取失败,park等待// 唤醒所有等待的线程(实际应该维护等待队列)// 这里简化处理特性说明无需synchronized可以在任何地方调用精确唤醒unpark(thread)精确唤醒指定线程。
park和unpark机制详解
LockSupport的park/unpark是Java并发中另一种重要的线程阻塞唤醒机制,相比wait/notify更加灵活强大。
什么是LockSupport
基本概念
LockSupport:是用来创建锁和其他同步类的线程原语(Thread Primitive)。
位置:java.util.concurrent.locks.LockSupport
作用:
- 🔧 提供更灵活的线程阻塞和唤醒机制
- 🔧 是构建其他并发工具的基础(如AQS)
- 🔧 比wait/notify更强大和易用
核心方法
public class LockSupport {
// 阻塞当前线程
public static void park()
// 阻塞当前线程,最多等待nanos纳秒
public static void parkNanos(long nanos)
// 阻塞当前线程,直到deadline时间点
public static void parkUntil(long deadline)
// 阻塞当前线程,带blocker对象(用于诊断)
public static void park(Object blocker)
public static void parkNanos(Object blocker, long nanos)
public static void parkUntil(Object blocker, long deadline)
// 唤醒指定线程
public static void unpark(Thread thread)
}
park/unpark基本使用
示例1:基本用法
import java.util.concurrent.locks.LockSupport;
public class ParkUnparkDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("start..."); // 1
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("park..."); // 2
LockSupport.park(); // 阻塞
System.out.println("resume..."); // 4
}, "t1");
t1.start();
Thread.sleep(2000); // 确保t1先park
System.out.println("unpark..."); // 3
LockSupport.unpark(t1); // 唤醒t1
}
}
输出:
start...
park...
unpark...
resume...
执行流程:
T1: t1启动,输出"start..."
T2: t1 sleep 1秒
T3: t1输出"park...",调用park()阻塞
T4: 主线程 sleep 2秒后,输出"unpark..."
T5: 主线程调用unpark(t1),唤醒t1
T6: t1恢复执行,输出"resume..."
示例2:可以先unpark
重要特性:park/unpark可以先unpark再park,效果一样!
public class UnparkFirstDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("start...");
try {
Thread.sleep(2000); // sleep 2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("park...");
LockSupport.park(); // 不会阻塞,立即继续
System.out.println("resume...");
}, "t1");
t1.start();
Thread.sleep(1000); // sleep 1秒
System.out.println("unpark...");
LockSupport.unpark(t1); // 先unpark(t1还在sleep中)
}
}
输出:
start...
unpark... ← 先unpark
park... ← 后park,但不阻塞!
resume... ← 立即继续执行
执行流程:
T1: t1启动,输出"start...",开始sleep 2秒
T2: 主线程sleep 1秒后,输出"unpark..."
T3: 主线程调用unpark(t1),设置许可为1
T4: t1 sleep结束,输出"park..."
T5: t1调用park(),发现有许可,消耗许可,不阻塞
T6: t1输出"resume..."
对比wait/notify:
// ❌ wait/notify不能先notify
synchronized (lock) {
lock.notify(); // 先notify
}
synchronized (lock) {
lock.wait(); // 后wait,会一直等待!
}
// ✅ park/unpark可以先unpark
LockSupport.unpark(t1); // 先unpark
LockSupport.park(); // 后park,不会阻塞
示例3:多次unpark只有一次效果
public class MultipleUnparkDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("start...");
LockSupport.park(); // 第一次park
System.out.println("第一次被唤醒");
LockSupport.park(); // 第二次park
System.out.println("第二次被唤醒");
LockSupport.park(); // 第三次park
System.out.println("第三次被唤醒");
}, "t1");
t1.start();
Thread.sleep(1000);
// 连续unpark 3次
System.out.println("第一次unpark");
LockSupport.unpark(t1);
System.out.println("第二次unpark");
LockSupport.unpark(t1);
System.out.println("第三次unpark");
LockSupport.unpark(t1);
}
}
输出:
start...
第一次unpark
第二次unpark
第三次unpark
第一次被唤醒
(程序卡住,t1在第二次park处阻塞)
原因:
- park/unpark使用**许可(permit)**机制
- 许可最多只有1个,多次unpark不会累加
- 第一次park消耗了唯一的许可,后面的park会阻塞
初始状态:许可 = 0
unpark(): 许可 = 1
unpark(): 许可 = 1 ← 不会变成2!
unpark(): 许可 = 1 ← 不会变成3!
park(): 许可 = 0 ← 消耗许可
park(): 阻塞! ← 没有许可了
示例4:带blocker参数
blocker参数用于诊断和监控,可以通过jstack看到阻塞原因:
public class BlockerDemo {
public static void main(String[] args) throws InterruptedException {
Object blocker = new Object();
Thread t1 = new Thread(() -> {
System.out.println("park with blocker");
LockSupport.park(blocker); // 带blocker
System.out.println("unparked");
}, "t1");
t1.start();
Thread.sleep(1000);
// 使用jstack查看:
// "t1" #12 prio=5 os_prio=0 waiting on condition
// parking to wait for <0x000000076b5bf630> (a java.lang.Object)
}
}
park/unpark vs wait/notify
对比表格
| 特性 | park/unpark | wait/notify |
|---|---|---|
| 调用条件 | 无需synchronized | 必须在synchronized块中 |
| 唤醒线程 | 精确唤醒指定线程 | notify随机唤醒一个 |
| 顺序要求 | 可以先unpark | 不能先notify |
| 许可机制 | 有许可(permit)概念 | 无 |
| 释放锁 | 不会释放锁 | wait会释放锁 |
| 所属类 | LockSupport(工具类) | Object(任何对象) |
| 使用难度 | 简单 | 较复杂 |
详细对比
1. 调用条件
// wait/notify:必须在synchronized块中
synchronized (lock) {
lock.wait();
}
synchronized (lock) {
lock.notify();
}
// park/unpark:无需synchronized
LockSupport.park();
LockSupport.unpark(thread);
2. 唤醒方式
// wait/notify:随机唤醒或全部唤醒
Object lock = new Object();
synchronized (lock) {
lock.notify(); // 随机唤醒一个
lock.notifyAll(); // 唤醒所有
}
// park/unpark:精确唤醒指定线程
LockSupport.unpark(t1); // 精确唤醒t1
LockSupport.unpark(t2); // 精确唤醒t2
3. 顺序要求
// wait/notify:不能先notify
synchronized (lock) {
lock.notify(); // 先notify无效
}
synchronized (lock) {
lock.wait(); // 后wait会一直等待
}
// park/unpark:可以先unpark
LockSupport.unpark(t1); // 先unpark有效
LockSupport.park(); // 后park不阻塞
4. 是否释放锁
// wait:释放锁
synchronized (lock) {
lock.wait(); // 释放lock,其他线程可以获取
}
// park:不释放锁
synchronized (lock) {
LockSupport.park(); // 不释放lock,其他线程无法获取
}
演示park不释放锁:
public class ParkNotReleaseLock {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("t1: 获得锁");
System.out.println("t1: 开始park");
LockSupport.park(); // park不释放锁
System.out.println("t1: park结束");
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2: 获得锁"); // 永远不会执行
}
}, "t2");
t1.start();
Thread.sleep(100);
t2.start();
Thread.sleep(100);
System.out.println("主线程: unpark t1");
LockSupport.unpark(t1); // 唤醒t1
}
}
输出:
t1: 获得锁
t1: 开始park
主线程: unpark t1
t1: park结束
t2: 获得锁 ← t1释放锁后,t2才能执行
park/unpark原理
许可(Permit)机制
park/unpark的核心是许可(permit):
许可的特点:
1. 许可只有0或1,不会累加
2. park()消耗许可(1 → 0)
3. unpark()增加许可(0 → 1,1 → 1不变)
4. 有许可时park()不阻塞
5. 无许可时park()阻塞
底层实现(_counter)
每个线程都有一个与LockSupport关联的许可(permit),在HotSpot中通过_counter变量实现:
// HotSpot源码(简化版)
class Parker : public os::PlatformParker {
private:
volatile int _counter; // 许可计数器(0或1)
pthread_mutex_t _mutex; // 互斥锁
pthread_cond_t _cond; // 条件变量
public:
void park() {
// 如果_counter > 0,消耗许可,不阻塞
if (_counter > 0) {
_counter = 0;
return;
}
// _counter == 0,阻塞等待
pthread_mutex_lock(&_mutex);
while (_counter == 0) {
pthread_cond_wait(&_cond, &_mutex);
}
_counter = 0;
pthread_mutex_unlock(&_mutex);
}
void unpark() {
pthread_mutex_lock(&_mutex);
_counter = 1; // 设置许可为1
pthread_cond_signal(&_cond); // 唤醒等待线程
pthread_mutex_unlock(&_mutex);
}
};
先park的情况
初始状态:_counter = 0
1. Thread调用park()
├─ 检查_counter == 0
├─ 获取_mutex互斥锁
├─ 进入_cond条件变量等待
└─ 线程阻塞
2. 调用unpark(Thread)
├─ 获取_mutex互斥锁
├─ 设置_counter = 1
├─ 唤醒_cond上等待的线程
└─ 释放_mutex
3. Thread被唤醒
├─ 从_cond等待中返回
├─ 设置_counter = 0
├─ 释放_mutex
└─ 继续执行
图示:
Thread Parker对象
│ _counter = 0
│ _mutex
│ _cond
│
├─ park()
│ ├─ 检查_counter == 0
│ ├─ 获取_mutex
│ ├─ 进入_cond等待
│ │ ⏸️ 阻塞中...
│ │
│ │ 另一个线程调用unpark()
│ │ ├─ 获取_mutex
│ │ ├─ _counter = 1
│ │ ├─ signal(_cond)
│ │ └─ 释放_mutex
│ │
│ ├─ 被唤醒 ✅
│ ├─ _counter = 0
│ └─ 释放_mutex
│
└─ 继续执行
先unpark的情况
初始状态:_counter = 0
1. 调用unpark(Thread)
├─ 获取_mutex互斥锁
├─ 设置_counter = 1
├─ signal(_cond)(没有线程等待,无效果)
└─ 释放_mutex
2. Thread调用park()
├─ 检查_counter == 1 ← 有许可!
├─ 设置_counter = 0
└─ 直接返回,不阻塞 ✅
图示:
Thread Parker对象
│ _counter = 0
│
│ 另一个线程先调用unpark()
│ ├─ 获取_mutex
│ ├─ _counter = 1 ← 设置许可
│ └─ 释放_mutex
│
├─ park()
│ ├─ 检查_counter == 1 ← 有许可!
│ ├─ _counter = 0 ← 消耗许可
│ └─ 直接返回 ✅
│
└─ 继续执行
多次unpark的情况
初始状态:_counter = 0
1. 第一次unpark()
└─ _counter = 1
2. 第二次unpark()
└─ _counter = 1 ← 不会变成2!
3. 第三次unpark()
└─ _counter = 1 ← 还是1
4. 第一次park()
└─ _counter = 0 ← 消耗许可
5. 第二次park()
└─ _counter == 0,阻塞!
实战应用
应用1:自定义锁
public class SimpleLock {
private volatile boolean locked = false;
private Thread owner;
public void lock() {
Thread current = Thread.currentThread();
while (true) {
// 尝试获取锁
if (!locked) {
synchronized (this) {
if (!locked) {
locked = true;
owner = current;
return;
}
}
}
// 获取失败,park等待
LockSupport.park();
}
}
public void unlock() {
synchronized (this) {
if (owner == Thread.currentThread()) {
locked = false;
owner = null;
// 唤醒所有等待的线程(实际应该维护等待队列)
// 这里简化处理
}
}
}
}
应用2:一次性屏障
public class OneShotLatch {
private volatile boolean opened = false;
private List<Thread> waiters = new ArrayList<>();
public void await() {
if (opened) {
return; // 已经打开,直接返回
}
Thread current = Thread.currentThread();
synchronized (this) {
if (opened) {
return;
}
waiters.add(current);
}
// park等待
LockSupport.park();
}
public void open() {
synchronized (this) {
if (opened) {
return;
}
opened = true;
// 唤醒所有等待的线程
for (Thread waiter : waiters) {
LockSupport.unpark(waiter);
}
waiters.clear();
}
}
public static void main(String[] args) throws InterruptedException {
OneShotLatch latch = new OneShotLatch();
// 创建5个线程等待
for (int i = 1; i <= 5; i++) {
int id = i;
new Thread(() -> {
System.out.println("线程" + id + " 等待...");
latch.await(); // 阻塞等待
System.out.println("线程" + id + " 通过!");
}).start();
}
Thread.sleep(2000);
System.out.println("\n主线程打开门栓");
latch.open(); // 打开,所有线程通过
}
}
输出:
线程1 等待...
线程2 等待...
线程3 等待...
线程4 等待...
线程5 等待...
主线程打开门栓
线程1 通过!
线程2 通过!
线程3 通过!
线程4 通过!
线程5 通过!
应用3:interrupt()中断park
park可以被interrupt()中断:
public class InterruptParkDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("t1: park...");
LockSupport.park();
System.out.println("t1: 被中断唤醒");
System.out.println("t1: 中断状态 = " +
Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
Thread.sleep(1000);
System.out.println("主线程: interrupt t1");
t1.interrupt(); // 中断t1
}
}
输出:
t1: park...
主线程: interrupt t1
t1: 被中断唤醒
t1: 中断状态 = true ← 中断标记不会被清除
注意:
- interrupt()可以唤醒park的线程
- 中断标记不会被清除(与wait不同)
- 如果中断标记为true,park会失效
// 演示:中断标记为true时,park失效
Thread t1 = new Thread(() -> {
System.out.println("park 1");
LockSupport.park();
System.out.println("unpark 1");
// 此时中断标记为true
System.out.println("中断状态: " + Thread.currentThread().isInterrupted());
System.out.println("park 2");
LockSupport.park(); // ❌ 失效!不会阻塞
System.out.println("unpark 2"); // 立即执行
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
解决方法:使用Thread.interrupted()清除标记
System.out.println("park 1");
LockSupport.park();
System.out.println("unpark 1");
// 清除中断标记
Thread.interrupted();
System.out.println("park 2");
LockSupport.park(); // ✅ 现在可以正常park
System.out.println("unpark 2");
🎯 知识点总结
park/unpark核心特性
| 特性 | 说明 |
|---|---|
| 无需synchronized | 可以在任何地方调用 |
| 精确唤醒 | unpark(thread)精确唤醒指定线程 |
| 可以先unpark | 先unpark再park不会阻塞 |
| 许可机制 | 许可只有0或1,不会累加 |
| 不释放锁 | park不会释放synchronized锁 |
| 可被中断 | interrupt()可以唤醒park |
底层原理
每个线程关联一个Parker对象:
├─ _counter:许可计数器(0或1)
├─ _mutex:互斥锁
└─ _cond:条件变量
park():
├─ 如果_counter > 0:消耗许可,不阻塞
└─ 如果_counter == 0:进入_cond等待,阻塞
unpark():
├─ 设置_counter = 1
└─ 唤醒_cond上的线程
使用建议
- ✅ 需要精确唤醒时使用park/unpark
- ✅ 不想写synchronized时使用park/unpark
- ✅ 构建自定义同步工具时使用
- ⚠️ 注意中断标记会影响park
- ⚠️ park不会释放synchronized锁
💡 常见面试题
Q1:park/unpark和wait/notify有什么区别?
答:
- park/unpark不需要synchronized,wait/notify必须在synchronized块中
- park/unpark可以精确唤醒指定线程,notify随机唤醒
- park/unpark可以先unpark,wait/notify不能先notify
- park不会释放锁,wait会释放锁
Q2:park/unpark的许可是如何工作的?
答:每个线程有一个关联的许可(permit),许可只有0或1两种状态。unpark()将许可设置为1,park()消耗许可(1→0)。如果有许可,park()不阻塞;如果无许可,park()阻塞。多次unpark()不会累加许可。
Q3:为什么先unpark再park不会阻塞?
答:因为unpark()会将许可设置为1。之后调用park()时,检测到有许可,就消耗许可并继续执行,不会阻塞。这类似于"提前消费"——先给你一个许可,你后面来使用时就不用等待了。
Q4:park会释放synchronized锁吗?
答:不会。park只是阻塞当前线程,不会释放任何锁。这与wait()不同,wait()会释放对象锁。
Q5:如何正确处理park被interrupt的情况?
答:
- park被interrupt唤醒后,中断标记会是true
- 中断标记为true时,park会失效(不阻塞)
- 需要用Thread.interrupted()清除中断标记,才能再次正常park
更多推荐


所有评论(0)