文章目录

Java LockSupport.park与unpark的线程阻塞唤醒机制

发布于 2026-04-19 12:19:10 · 浏览 7 次 · 评论 0 条

Java LockSupport.park与unpark的线程阻塞唤醒机制

LockSupport 是 Java 并发包 (java.util.concurrent.locks) 中最基础的线程阻塞原语,用于构建锁和其他同步工具。与传统的 Object.wait/notify 相比,它不需要获取监视器锁,能够更灵活地阻塞和唤醒指定线程。


一、 理解核心机制:许可 (Permit)

LockSupport 的核心在于一个名为“许可” (Permit) 的概念。可以把这个许可看作一张“通行证”。

  • 默认状态:每个线程都有一个许可,默认状态是 0 (不可用)
  • park 行为:当线程调用 LockSupport.park() 时,如果许可可用 (值为1),它会消耗掉这个许可 (变为0) 并立即返回;如果许可不可用 (值为0),线程会被阻塞。
  • unpark 行为:当调用 LockSupport.unpark(Thread thread) 时,它会将目标线程的许可设置为 1 (可用)。如果线程此前因调用 park 而被阻塞,它会被唤醒;如果线程尚未阻塞,这次调用保证了它下一次调用 park 时不会阻塞。

以下流程展示了 park 与许可的状态流转逻辑:

graph TD A["调用 LockSupport.park()"] --> B{"许可 Permit == 1?"} B -- Yes --> C["消费许可 (变为 0)"] C --> D["立即返回继续执行"] B -- No --> E["线程进入阻塞状态"] E --> F["等待其他线程调用 unpark"] F --> G["许可被设置为 1"] G --> D

二、 基础阻塞与唤醒操作

编写 以下代码,演示最标准的“先阻塞后唤醒”流程。

创建 一个新线程,在线程内部 调用 LockSupport.park()

启动 线程,此时主线程 休眠 1秒以模拟业务耗时。

调用 LockSupport.unpark(t) 为该线程提供许可。

public class BasicParkExample {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("子线程:准备开始阻塞...");
            // 阻塞当前线程,等待许可
            LockSupport.park(); 
            System.out.println("子线程:已被唤醒,继续执行!");
        });

        t.start();
        Thread.sleep(1000); // 模拟主线程忙

        System.out.println("主线程:准备唤醒子线程");
        LockSupport.unpark(t); // 唤醒 t 线程
    }
}

注意unpark 可以在 park 之前调用。如果先执行了 unpark,许可变为1,随后线程执行 park 时发现许可可用,会直接消费掉许可而不会阻塞。


三、 响应线程中断

LockSupport.park() 能够响应中断,但与 Object.wait() 不同,它不会抛出 InterruptedException

运行 以下代码观察中断行为。

定义 一个子线程并在其中 调用 LockSupport.park()

在主线程中 调用 t.interrupt() 中断子线程。

public class InterruptParkExample {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("子线程:开始阻塞");
            LockSupport.park();
            System.out.println("子线程:从park中返回");

            // 检查中断状态
            System.out.println("中断状态: " + Thread.currentThread().isInterrupted());
        });

        t.start();
        Thread.sleep(2000);

        System.out.println("主线程:发送中断信号");
        t.interrupt();
    }
}

执行 结果分析:

  1. 线程从 park 返回。
  2. Thread.currentThread().isInterrupted() 返回 true,说明中断标志位被保留。

关键特性

  • 如果不清除中断标志,下次再循环调用 LockSupport.park() 时,因为线程中断状态仍然存在,park 方法会直接返回,无法再次阻塞。
  • 调用 Thread.interrupted() 可以清除中断标志,使下次 park 能够正常阻塞。

四、 避免不可重入陷阱

LockSupport 的许可是不可累积的,且 不支持重入。如果同一个线程连续调用两次 park(),而中间只调用了一次 unpark(),第二次 park 将会导致永久阻塞(死锁)。

执行 以下代码验证不可重入性。

public class NonReentrantExample {
    public static void main(String[] args) {
        Thread current = Thread.currentThread();

        // 提前发放一次许可
        LockSupport.unpark(current); 

        System.out.println("步骤 1");
        LockSupport.park(); // 消费许可,正常运行
        System.out.println("步骤 2");

        // 此时许可已被消耗,变为 0
        LockSupport.park(); // 再次阻塞,且不会再有 unpark 唤醒它
        System.out.println("步骤 3 (永远不会打印)");
    }
}

切记:每一次 park 调用,都必须对应一次 unpark 调用。


五、 对比传统 wait/notify

为了更清晰地理解 LockSupport 的优势,下表对比了它与 Object.wait/notify 的区别。

特性 Object.wait / notify LockSupport.park / unpark
锁要求 必须持有对象监视器锁 (synchronized) 不需要持有任何锁
唤醒对象 只能随机唤醒一个线程或全部唤醒 可以精准唤醒指定的线程
调用顺序 必须先 wait,后 notify 支持 park 后 unpark,也支持先 unpark 后 park
中断响应 抛出 InterruptedException 返回,不抛出异常,需手动检查状态

总结
在使用 LockSupport 时,请记住它是一种基于“许可”的轻量级同步机制。它通过 Unsafe 类直接操作操作系统层面的线程状态,脱离了 JVM 监视器锁的束缚,是实现高性能并发组件(如 ReentrantLock, CountDownLatch)的基石。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文