LockSupport详解以及使用
LockSupport
是 Java 并发编程中一个非常基础但极其重要的工具类,它是 java.util.concurrent
(JUC)包的底层核心之一,被广泛用于线程阻塞与唤醒。
🔍 一、LockSupport 是什么?
LockSupport
是 JDK 提供的线程阻塞和唤醒工具类,位于 java.util.concurrent.locks
包中。
它不像 synchronized
或 ReentrantLock
那样直接用于同步控制,而是提供了一种更底层、更灵活的线程挂起(park)和唤醒(unpark)机制。
✅ 核心方法:
LockSupport.park()
:阻塞当前线程LockSupport.unpark(Thread thread)
:唤醒指定线程
🧱 二、为什么需要 LockSupport?对比传统方式
特性 | wait()/notify() | LockSupport.park()/unpark() |
---|---|---|
所属机制 | synchronized | JUC 并发包底层 |
是否需要锁 | ✅ 必须在 synchronized 块中 | ❌ 不需要 |
唤醒是否必须在阻塞后 | ❌ 唤醒前调用 notify() 会丢失 | ✅ 可以先 unpark() ,再 park() |
唤醒指定线程 | ❌ 只能随机唤醒一个或全部 | ✅ 可指定具体线程 |
精确控制 | ❌ 不够灵活 | ✅ 更加灵活、可靠 |
底层支持 | Object 监视器 | Unsafe 类 + 线程许可(permit)机制 |
🧩 三、核心原理:许可(Permit)机制
LockSupport
的核心是 “许可”(Permit) 概念:
- 每个线程都有一个与之关联的许可(boolean 类型,0 或 1)
- 调用
unpark(thread)
:给该线程发放一个许可(最多只有一个,重复调用无效) - 调用
park()
:尝试消耗这个许可- 如果有许可 → 立即返回(不阻塞)
- 如果无许可 → 阻塞,直到其他线程调用
unpark()
给它一个许可
✅ 关键特性:
unpark()
可以在park()
之前调用,不会丢失!
🧪 四、基本使用示例
✅ 示例 1:最简单的 park/unpark
public class LockSupportDemo {public static void main(String[] args) throws InterruptedException {Thread worker = new Thread(() -> {System.out.println("线程 " + Thread.currentThread().getName() + " 开始工作");// 阻塞自己System.out.println("线程 " + Thread.currentThread().getName() + " 即将 park");LockSupport.park(); // 阻塞System.out.println("线程 " + Thread.currentThread().getName() + " 被唤醒,继续执行");}, "Worker");worker.start();Thread.sleep(2000); // 主线程等待 2 秒// 唤醒 worker 线程System.out.println("主线程即将 unpark Worker");LockSupport.unpark(worker);worker.join(); // 等待 worker 结束}
}
输出:
✅ 示例 2:unpark()
在 park()
之前调用(不会丢失)
Thread worker = new Thread(() -> {System.out.println("Worker 线程开始");// 先 unpark 再 park,不会阻塞!LockSupport.unpark(Thread.currentThread());System.out.println("Worker 线程即将 park");LockSupport.park(); // 因为已有许可,立即返回,不阻塞System.out.println("Worker 线程继续执行");
});worker.start();
worker.join();
✅ 输出中不会卡住,说明
unpark()
提前调用有效。
✅ 示例 3:多次 unpark()
只保留一个许可
Thread worker = new Thread(() -> {LockSupport.unpark(Thread.currentThread()); // 第一次LockSupport.unpark(Thread.currentThread()); // 第二次(无效)System.out.println("许可已发放");LockSupport.park(); // 消耗许可,继续System.out.println("第一次 park 返回");LockSupport.park(); // 再次 park,这次会阻塞(因为无许可)System.out.println("第二次 park 返回(不会执行)");
});worker.start();
Thread.sleep(3000);
// 主动唤醒,否则线程会一直阻塞
LockSupport.unpark(worker);
✅ 说明:许可不会累积,最多只有一个。
🛠 五、高级用法与参数
1. park(Object blocker)
:带阻塞原因的 park
LockSupport.park("等待数据库连接"); // blocker 对象,用于诊断
blocker
参数通常是一个对象,表示线程为什么被阻塞- 在线程 dump(如 jstack)中可以看到阻塞原因,极大提升调试能力
Thread worker = new Thread(() -> {System.out.println("线程开始");LockSupport.park("等待任务队列有任务"); // 便于排查死锁System.out.println("被唤醒");
});
✅ 推荐始终使用
park(blocker)
而不是无参版本。
2. parkNanos(long nanos)
:限时阻塞
LockSupport.parkNanos(1_000_000_000); // 阻塞最多 1 秒
- 阻塞指定纳秒数,超时自动唤醒
- 类似
Thread.sleep()
,但可被unpark()
提前唤醒
3. parkUntil(long deadline)
:阻塞到某个时间点
long deadline = System.currentTimeMillis() + 5000; // 5 秒后
LockSupport.parkUntil(deadline);
🏗 六、LockSupport 在 JUC 中的应用(源码级解析)
LockSupport
是 AQS(AbstractQueuedSynchronizer) 的底层支撑,几乎所有 JUC 同步器都基于它:
同步器 | 使用方式 |
---|---|
ReentrantLock | acquire() 中调用 LockSupport.park() 阻塞线程 |
Semaphore | 获取信号量失败时 park |
CountDownLatch | await() 时 park,countDown() 时 unpark |
ThreadPoolExecutor | 工作线程空闲时 park |
🔍 源码片段(简化版 AQS 获取锁逻辑)
final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);return interrupted;}// 阻塞当前线程LockSupport.park(this); // 核心在这里!if (Thread.interrupted()) interrupted = true;}} catch (Throwable t) {cancelAcquire(node);throw t;}
}
✅ 当线程获取锁失败时,AQS 会将其加入等待队列,并调用
LockSupport.park()
阻塞。
⚠️ 七、常见问题与注意事项
❌ 问题 1:park()
不会响应中断?
LockSupport.park()
不会抛出InterruptedException
- 但它会设置线程的中断状态(
Thread.interrupted()
返回 true)
Thread worker = new Thread(() -> {System.out.println("即将 park");LockSupport.park();System.out.println("被唤醒,中断状态: " + Thread.currentThread().isInterrupted());
});worker.start();
worker.interrupt(); // 中断
✅ 输出:
被唤醒,中断状态: true
✅ 所以:
park()
不会像wait()
那样抛异常,但你可以手动检查中断状态。
✅ 正确处理中断的写法:
while (!Thread.currentThread().isInterrupted()) {if (someCondition) break;LockSupport.park();
}
❌ 问题 2:unpark()
传 null 会怎样?
LockSupport.unpark(null); // ❌ 会抛出 NullPointerException!
✅ 必须确保线程不为 null。
✅ 八、最佳实践总结
场景 | 推荐做法 |
---|---|
阻塞线程 | LockSupport.park(blocker) (带 blocker) |
唤醒线程 | LockSupport.unpark(targetThread) |
限时阻塞 | parkNanos() 或 parkUntil() |
调试诊断 | 使用 blocker 参数,便于 jstack 分析 |
中断处理 | 手动检查 isInterrupted() ,不要依赖异常 |
避免死锁 | 确保 unpark() 一定会被调用 |
📊 九、与其他阻塞方式对比
方式 | 是否需要锁 | 可指定线程 | 可提前 unpark | 中断行为 |
---|---|---|---|---|
wait() | ✅ | ❌ | ❌ | 抛 InterruptedException |
sleep() | ❌ | ❌ | ❌ | 抛 InterruptedException |
park() | ❌ | ✅ | ✅ | 设置中断状态,不抛异常 |
🎯 十、总结
特性 | 说明 |
---|---|
定位 | JUC 并发包的底层线程阻塞工具 |
核心方法 | park() , unpark() |
核心机制 | 基于“许可(permit)”的阻塞唤醒 |
最大优势 | unpark() 可在 park() 前调用,不会丢失 |
调试支持 | 支持 blocker 参数,jstack 可见阻塞原因 |
应用场景 | AQS、线程池、自定义同步器、协程模拟等 |
🚀 一句话口诀
“
park
阻塞我,unpark
解放我;
许可在手,顺序不管;
带上 blocker,调试不抓瞎。”
实例:实现一个基于 LockSupport 的简化版 Semaphore
public class SimpleSemaphore {private final int permits;private volatile int available;private final Object lock = new Object();private final Thread[] waiters;private int front = 0;private int rear = 0;private final int queueCapacity;public SimpleSemaphore(int permits) {if (permits < 0) throw new IllegalArgumentException("permits < 0");this.permits = permits;this.available = permits;this.queueCapacity = 100;this.waiters = new Thread[queueCapacity];}public void acquire() throws InterruptedException {synchronized (lock) {if (available > 0) {available--;System.out.println(Thread.currentThread().getName() + " 获取许可,剩余: " + available);return;}if (rear - front >= queueCapacity) {throw new RuntimeException("等待队列已满");}waiters[rear++] = Thread.currentThread();System.out.println(Thread.currentThread().getName() + " 进入等待队列,队列长度: " + (rear - front));}LockSupport.park(this);if (Thread.interrupted()) {throw new InterruptedException();}}public void release() {synchronized (lock) {available++;if (front < rear) {Thread thread = waiters[front++];System.out.println("唤醒线程: " + thread.getName());LockSupport.unpark(thread);} else {System.out.println("释放许可,当前可用: " + available);}}}// 获取可用许可数public int getAvailablePermits() {synchronized (lock) {return available;}}
public class TestSimpleSemaphore {public static void main(String[] args) throws InterruptedException {SimpleSemaphore semaphore = new SimpleSemaphore(2); // 2 个许可for (int i = 1; i <= 5; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " 尝试获取许可...");semaphore.acquire();// 模拟工作System.out.println(Thread.currentThread().getName() + " 正在工作...");Thread.sleep(2000);semaphore.release();System.out.println(Thread.currentThread().getName() + " 释放许可");} catch (InterruptedException e) {Thread.currentThread().interrupt();}}, "Worker-" + i).start();}// 主线程等待Thread.sleep(15000);}
}
测试结果