简介: LockSupport是Java并发编程的底层基石,提供park()和unpark()方法实现线程阻塞与精确唤醒。基于“许可证”机制,无需同步块、调用顺序灵活、可精准控制线程,是ReentrantLock、CountDownLatch等高级同步工具的底层支撑,堪称JUC的“手术刀”。
- 引言
- 一、LockSupport是什么?
- 二、为什么需要LockSupport?
- 三、核心API:park()与unpark()
- 四、LockSupport的特性与优势
- 五、实战应用:手写一个简易锁
- 六、总结与最佳实践
- 互动环节
引言
在Java并发编程的世界里,我们已经熟悉了synchronized、ReentrantLock、CountDownLatch等工具。但你是否想过,这些强大的同步工具底层是如何实现线程的阻塞与唤醒的?
答案是:LockSupport。这个看似简单的工具类,却是整个JUC包最底层的基石之一。它就像道路系统中的交警,能够精准地让任何线程"停下"(阻塞)或"放行"(唤醒)。今天,就让我们揭开它的神秘面纱。
一、LockSupport是什么?
java.util.concurrent.locks.LockSupport是一个线程阻塞工具类,所有方法都是静态方法,可以直接调用。它的核心功能就两个:
- 阻塞(park):让当前线程等待(阻塞)。
- 唤醒(unpark):唤醒一个被阻塞的指定线程。
它最核心的概念是 "许可证(permit)"。你可以把它想象成一个只有一个令牌的令牌桶,但逻辑与常规相反:
- unpark(thread):如果线程thread还没有许可证,则给它发放一个许可证。最多只能有一个。
- park():消耗掉当前线程的许可证(如果有的话),并立即返回。如果当前线程没有许可证,那么它就必须等待,直到有其他线程调用unpark给它发放许可证,或者被中断。
二、为什么需要LockSupport?
在LockSupport出现之前,我们主要使用Object.wait()和Object.notify()/notifyAll()来阻塞和唤醒线程。但这种方式有诸多限制:
- 必须在synchronized同步块中使用,否则会抛出IllegalMonitorStateException。
- wait()和notify()的调用顺序必须严格保证。如果先调用notify()再调用wait(),线程将永远无法被唤醒。
- notify()是随机唤醒,不能精确唤醒某个指定的线程。
LockSupport的出现完美地解决了这些问题,它为构建更高级的同步工具(如ReentrantLock、CountDownLatch等)提供了灵活、可靠的底层基础。
三、核心API:park()与unpark()
LockSupport的API非常简洁,最核心的就是以下几个方法:
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": 开始执行,即将被阻塞...");
// 1. park() - 阻塞当前线程
// 如果此时有许可证,则消耗掉许可证并继续运行;如果没有,则等待。
LockSupport.park();
System.out.println(Thread.currentThread().getName() + ": 被唤醒了,继续执行!");
}, "示例线程");
thread.start();
// 主线程睡眠2秒,确保子线程先执行并先调用park()
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ": 准备唤醒子线程");
// 2. unpark(Thread thread) - 唤醒指定的线程
// 给`thread`线程发放一个许可证(如果它还没有的话)。
LockSupport.unpark(thread);
}
}
输出:
示例线程: 开始执行,即将被阻塞... main: 准备唤醒子线程 示例线程: 被唤醒了,继续执行!
其他常用方法:
- parkNanos(long nanos):阻塞当前线程,最长不超过指定的纳秒时间。超时后自动唤醒。
- parkUntil(long deadline):阻塞当前线程,直到某个绝对的截止时间(从1970年开始的毫秒数)。
- park(Object blocker):与park()功能相同,但允许传入一个blocker对象,用于记录线程被阻塞的原因,方便问题排查和监控(强烈推荐使用这种方式)。
四、LockSupport的特性与优势
- 调用顺序灵活:unpark()可以在park()之前调用。先发许可证,后park()会直接消耗许可证而不会阻塞。这解决了wait()/notify()的顺序死锁问题。
- java
- public static void main(String[] args) { Thread mainThread = Thread.currentThread(); // 先发放许可证 LockSupport.unpark(mainThread); System.out.println("先调用unpark"); // 再park,此时看到有许可证,直接消耗并返回,不会阻塞 LockSupport.park(); System.out.println("再调用park,也不会阻塞"); }
- 精确唤醒:unpark(Thread thread)可以精确指定要唤醒的线程,而不像notify()那样随机。
- 无需锁环境:可以在任何地方调用,不需要先获得某个对象的监视器锁。
- 可响应中断:线程在park()阻塞时,如果被其他线程中断(interrupt()),它会立即返回,但不会抛出InterruptedException。可以通过Thread.interrupted()方法检查中断标志。
五、实战应用:手写一个简易锁
理解了LockSupport,我们其实可以模仿AQS(
AbstractQueuedSynchronizer)的思路,实现一个非常简单的不可重入锁。
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.atomic.AtomicReference;
/**
* 一个基于LockSupport的简易互斥锁(不可重入)
*/
public class MiniLock {
// 使用原子引用,记录当前持有锁的线程
private final AtomicReference<Thread> owner = new AtomicReference<>();
// 等待队列,这里简单使用链表结构。AQS中是一个真正的CLH队列
private volatile Node waiters;
private static class Node {
final Thread thread;
volatile Node next;
Node(Thread thread) {
this.thread = thread;
}
}
public void lock() {
Thread current = Thread.currentThread();
// 尝试通过CAS获取锁
while (!owner.compareAndSet(null, current)) {
// 获取失败,将自己加入等待队列
Node node = new Node(current);
node.next = waiters;
waiters = node;
// 然后park自己
LockSupport.park(this); // 传入this作为blocker
// 被唤醒后,并不代表立刻拿到锁了,需要重新进入循环尝试CAS
}
// 成功获取锁,退出方法
}
public void unlock() {
Thread current = Thread.currentThread();
if (owner.compareAndSet(current, null)) {
// 释放锁成功,需要唤醒等待队列中的一个线程
if (waiters != null) {
// 这里简单唤醒队列中的第一个线程(非公平锁策略)
Node first = waiters;
if (first != null) {
waiters = first.next; // 从队列中移除
LockSupport.unpark(first.thread); // 唤醒它
}
}
}
}
// 测试我们的MiniLock
private static int count = 0;
private static final MiniLock lock = new MiniLock();
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + count); // 正确输出 20000
}
}
这个例子极大地简化了AQS的实现,但它清晰地展示了LockSupport如何作为构建更高级同步器的基石。
六、总结与最佳实践
- 底层基石:LockSupport是JUC包中许多高级同步工具(如ReentrantLock, CountDownLatch, Semaphore)的底层阻塞/唤醒机制。
- 核心机制:基于许可证(permit) 的逻辑,unpark发证,park消费证。
- 核心优势:
- 顺序无关性:unpark先于park调用也不会导致线程永久阻塞。
- 精确控制:可以指定要唤醒的线程。
- 灵活性:无需在同步块中调用。
- 最佳实践:
- 总是使用park(Object blocker):传入相关的同步对象(如this),这可以在使用jstack等工具诊断线程问题时,清晰地看到线程被哪个对象阻塞,极大提升调试效率。
- 在循环中检查条件:和传统的等待机制一样,被park唤醒后,必须重新检查等待条件是否真正满足,因为唤醒可能源于伪唤醒或超时。
- 理解中断响应:线程被中断后park会返回,但不会抛异常,记得检查中断状态。
LockSupport是Java并发工具箱中一把小巧而强大的"手术刀"。虽然我们在日常开发中直接使用它的场景不多,但理解其原理,能让我们对JUC包的整体理解上升到一个新的高度,也能在需要构建特定同步原语时得心应手。

被折叠的 条评论
为什么被折叠?



