一文搞懂LockSupport原理

本文会先梳理 LockSupport 的核心原理,再逐一对比它与 synchronized、Thread.sleep()、Object.wait()、Condition.await() 的区别,同时解答 notify()/unpark() 提前调用的关键问题,帮你彻底理清 Java 并发中线程阻塞/唤醒的核心逻辑。

一、LockSupport 核心原理回顾

LockSupport 是 JDK 并发包的底层工具类,核心是许可(Permit)机制

  • 每个线程绑定一个「许可」,许可只有两种状态:可用(1)、不可用(0),且最多只能有 1 个许可(不可累加);
  • park():尝试获取许可,获取到则消耗许可(变为 0)并直接返回;获取不到则阻塞线程,直到被唤醒/中断/超时;
  • unpark(Thread t):为线程 t 发放许可(若已存在则无效果),若线程正阻塞在 park() 上则唤醒它;
  • 底层依赖 sun.misc.Unsafe 调用 JVM 的 Parker 类,最终通过操作系统的 futex 系统调用实现线程挂起/唤醒。

二、各类同步方法的核心区别对比

1. LockSupport vs synchronized

维度 LockSupport synchronized
核心作用 线程阻塞/唤醒的底层工具 实现方法/代码块的互斥与同步
底层实现 Unsafe + Parker + 操作系统 futex 偏向锁→轻量级锁→重量级锁(监视器锁)
使用方式 静态方法直接调用(park/unpark) 修饰方法/代码块(隐式获取/释放锁)
阻塞/唤醒粒度 精准唤醒指定线程(unpark(Thread t)) 随机唤醒一个(notify())/全部(notifyAll())
前置条件 无需持有任何锁 必须先获取对象的监视器锁
中断响应 响应中断,返回后需手动检查中断状态 响应中断,抛出 InterruptedException
灵活性 极高(支持超时、先unpark后park安全) 较低(锁释放仅在代码块结束/异常时)

核心差异:synchronized 是「锁」,侧重资源互斥;LockSupport 是「线程控制工具」,是实现锁的基础。比如 ReentrantLock 内部就是通过 AQS 调用 LockSupport 实现线程阻塞。

2. Thread.sleep() vs Object.wait()

维度 Thread.sleep(long ms) Object.wait()
核心作用 让线程休眠指定时间,不释放资源 让线程等待,释放持有的监视器锁
前置条件 无(可在任意位置调用) 必须在 synchronized 块中(持有对象锁)
锁释放 不释放任何锁(包括 synchronized 锁) 释放当前对象的监视器锁
唤醒方式 超时自动唤醒 / 线程中断 notify()/notifyAll() 唤醒 / 中断 / 超时
中断处理 抛出 InterruptedException 抛出 InterruptedException
状态变化 线程进入 TIMED_WAITING 线程进入 WAITING/TIMED_WAITING

示例对比

// sleep 不释放锁
synchronized (obj) {
    Thread.sleep(1000); // 持有锁休眠,其他线程无法获取 obj 锁
}

// wait 释放锁
synchronized (obj) {
    obj.wait(1000); // 释放 obj 锁,其他线程可获取;超时后重新竞争锁
}

3. Object.wait() vs Condition.await()

Condition 是 JUC 包中配合 Lock 使用的同步工具(如 ReentrantLock.newCondition()),本质是 LockSupport 的上层封装:

维度 Object.wait() Condition.await()
依赖锁类型 synchronized 监视器锁 Lock 接口实现(如 ReentrantLock)
等待队列数量 每个对象只有 1 个等待队列 每个 Condition 对应 1 个等待队列(可多个)
唤醒粒度 只能随机唤醒一个/全部 可精准唤醒指定 Condition 队列的线程
方法丰富度 仅 wait()/wait(long) 支持 await()/awaitNanos()/awaitUntil()/awaitUninterruptibly() 等
中断响应 抛出 InterruptedException 可选择响应中断(await())或不响应(awaitUninterruptibly())

核心差异:Condition 解决了 synchronized 中 wait/notify 唤醒粒度不足的问题,比如生产者消费者模型中,可分别创建「生产者 Condition」和「消费者 Condition」,精准唤醒对应线程。

4. Thread.sleep() vs LockSupport.park()

维度 Thread.sleep(long ms) LockSupport.park()
核心机制 纯时间驱动的休眠 许可机制驱动的阻塞
锁释放 不释放任何锁 不释放任何锁
唤醒方式 超时自动唤醒 / 中断 unpark() 唤醒 / 中断 / 超时(parkNanos)
前置条件
先唤醒后阻塞 不支持(sleep(1000) 必须等 1000ms) 支持(先 unpark 后 park 直接返回)
中断处理 抛出 InterruptedException 响应中断但不抛异常(需手动检查)
诊断性 无阻塞原因记录 支持 park(blocker) 记录阻塞原因

关键区别:sleep 是「被动等待时间结束」,park 是「主动等待许可」;park 更灵活,是并发工具的底层选择,sleep 仅用于简单的时间休眠。

5. Object.wait() vs LockSupport.park()

维度 Object.wait() LockSupport.park()
前置条件 必须持有对象监视器锁(synchronized) 无(可在任意位置调用)
锁释放 释放监视器锁 不释放任何锁
唤醒方式 notify()/notifyAll()(随机/全部) unpark(Thread t)(精准指定线程)
先唤醒后阻塞 不安全(先 notify 后 wait 会丢失唤醒) 安全(先 unpark 后 park 直接返回)
许可机制 基于许可(Permit)机制
中断处理 抛出 InterruptedException 响应中断但不抛异常

核心差异:wait() 是「同步锁层面的等待」(释放锁),park() 是「线程层面的阻塞」(不释放锁);park 无需依赖锁,灵活性和安全性更高。

三、关键问题解答

1. 如果在 wait() 之前执行了 notify() 会怎样?

结果:线程调用 wait() 后会永久阻塞(除非被中断)。
原因

  • wait()/notify() 依赖对象的「等待队列」:notify() 会唤醒队列中一个线程,但如果调用 notify() 时队列中无等待线程,这个唤醒信号会丢失
  • 后续线程调用 wait() 时,会进入等待队列,但已没有 notify() 信号唤醒它,最终线程会一直阻塞。

示例验证

public class WaitNotifyTest {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        
        // 先调用 notify()
        synchronized (obj) {
            obj.notify(); // 此时无等待线程,信号丢失
        }
        
        // 后调用 wait() → 永久阻塞
        new Thread(() -> {
            synchronized (obj) {
                try {
                    obj.wait(); // 无 notify() 唤醒,一直阻塞
                    System.out.println("线程被唤醒"); // 永远不会执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

2. 如果在 park() 之前执行了 unpark() 会怎样?

结果:线程调用 park() 后直接返回,不会阻塞。
原因

  • LockSupport 基于「许可机制」,unpark() 会为线程发放许可(最多 1 个);
  • 即使线程还未调用 park(),许可也会被保存,后续 park() 会直接消耗许可并返回,不会阻塞。

示例验证

public class ParkUnparkTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                // 先等待 1 秒,让主线程先执行 unpark()
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程准备调用 park()");
            LockSupport.park(); // 许可已存在,直接返回
            System.out.println("线程 park() 后继续执行");
        });
        
        t.start();
        // 先调用 unpark() 发放许可
        LockSupport.unpark(t);
        System.out.println("主线程已执行 unpark()");
    }
}

输出结果

主线程已执行 unpark()
线程准备调用 park()
线程 park() 后继续执行

四、总结

  1. LockSupport 核心:基于「许可机制」实现线程阻塞/唤醒,无锁依赖、支持精准唤醒、先 unpark 后 park 安全,是 JUC 锁的底层基础;
  2. 核心区别关键点
    • sleep 不释放锁、时间驱动;wait 释放监视器锁、依赖 synchronized;park 不释放锁、许可驱动;
    • Condition 是 wait/notify 的增强版,支持多队列精准唤醒;
  3. 关键问题结论
    • 先 notify 后 wait → 信号丢失,线程永久阻塞;
    • 先 unpark 后 park → 许可保留,线程直接返回,无阻塞。
posted @ 2026-03-06 21:26  七星6609  阅读(1)  评论(0)    收藏  举报