【大白话说Java面试题 第122题】【并发篇】第22题:wait、park 和 sleep 的区别?

📌 异常处理Java开发基于Spring Boot的异常处理框架设计:电商系统业务异常建模与全局统一响应实现

第22题:wait、park 和 sleep 的区别?

📚 回答:

  • 核心考点wait()park()sleep() 是 Java 中三种让线程暂停执行的机制,但大厂面试不会只问"方法归属、唤醒方式、锁特性",而是深入考察 底层实现原理 (ObjectMonitor vs Unsafe.park vs 系统调用)、许可机制(Permit)先 unpark 后 park 的语义保证中断响应的差异 ,以及 AQS 为什么用 LockSupport 而不是 wait/notify。面试官真正想判断的是:你是否能从 JVM 源码层面理解三种机制的本质差异,并能在工程实践中做出正确选型。

1. 七大维度全面对比
对比维度 wait() / wait(long) LockSupport.park() Thread.sleep(long) 面试踩坑点
方法归属 Object 实例方法 LockSupport 静态方法 Thread 静态方法 park 不是 Thread 的方法!
调用前提 必须持有对象锁 无限制 无限制 无锁调用 wait 抛异常
锁行为 释放锁 不释放锁 不释放锁 park/sleep 在同步块内会阻塞其他线程
唤醒方式 notify() / notifyAll() / 中断 / 超时 unpark() / 中断 超时自动恢复 / 中断 park 不支持 notify!
许可机制 (二元许可,最多累积1个) 先 unpark 后 park 不会阻塞
线程状态 WAITING / TIMED_WAITING WAITING / TIMED_WAITING TIMED_WAITING park 也是 WAITING
底层实现 ObjectMonitor + 条件队列 Unsafe.park(操作系统线程调度) os::sleep(系统定时器) 三者底层完全不同
使用场景 线程协作(生产者-消费者) AQS、自定义同步器、无锁编程 延时执行、定时轮询 park 不直接用于业务

2. 底层实现原理深度解析
  • 2.1 wait() 的底层------ObjectMonitor 条件队列

wait() 依赖 HotSpot 的 ObjectMonitor(C++ 实现),核心流程:

cpp 复制代码
// ObjectMonitor::wait() 核心逻辑(简化)
void ObjectMonitor::wait(TRAPS) {
    Thread* Self = THREAD;

    // 1. 安全检查:必须持有锁
    if (_owner != Self) {
        THROW(vmSymbols::java_lang_IllegalMonitorStateException());
    }

    // 2. 创建 ObjectWaiter 节点
    ObjectWaiter node(Self);

    // 3. 释放锁(_owner = NULL)
    exit(true, Self);

    // 4. 加入 _WaitSet 条件队列
    node._next  = _WaitSet;
    _WaitSet    = &node;

    // 5. 挂起线程(park)
    park();

    // 6. 被唤醒后:从 _WaitSet 移除,重新竞争锁
    enter(Self);
}

关键特征wait() 释放锁是为了让其他线程能够获取锁并执行 notify(),否则将发生死锁。被唤醒后必须重新竞争锁才能从 wait() 返回。citation:0

  • 2.2 park() 的底层------Unsafe + 操作系统线程调度

LockSupport.park() 是 Java 并发包的基石,AQS、Semaphore、CountDownLatch 等全部基于它实现。底层调用 sun.misc.Unsafe.park()

cpp 复制代码
// Unsafe.park() 底层实现(OpenJDK,简化)
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, 
                                jboolean isAbsolute, jlong time)) {
    // 1. 检查是否已中断
    if (Thread::is_interrupted(thread, false)) {
        return; // 直接返回,不阻塞
    }

    // 2. 检查 permit:如果有许可,消费掉并直接返回
    if (thread->osthread()->try_set_permit(false)) {
        return;
    }

    // 3. 没有许可 → 挂起线程
    thread->osthread()->set_state(MONITOR_WAIT);

    // Linux: pthread_cond_wait
    // Windows: WaitForSingleObject
    os::park(thread, isAbsolute, time);

    // 4. 被唤醒后:检查中断状态
    if (Thread::is_interrupted(thread, true)) {
        // 设置中断标志,但不抛异常
    }
}
UNSAFE_END

核心特征

  1. 无锁操作park() 不依赖任何对象锁,直接操作线程调度器。
  2. permit 机制 :每个线程有一个二元许可(0 或 1),unpark() 发放许可,park() 消费许可。许可最多累积 1 个。
  3. 跨平台 :Linux 用 pthread_cond_wait,Windows 用 WaitForSingleObjectcitation:1
  • 2.3 sleep() 的底层------操作系统定时器
cpp 复制代码
// JVM_Sleep 底层实现(简化)
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis)) {
    // 1. 检查中断状态
    if (Thread::is_interrupted(THREAD, true)) {
        THROW_MSG(vmSymbols::java_lang_InterruptedException(), ...);
    }

    // 2. 记录开始时间
    jlong prev_time = javaTimeNanos();

    // 3. 挂起线程(不操作 permit,不操作 Monitor)
    thread->osthread()->set_state(MONITOR_WAIT);
    os::sleep(thread, millis, false);

    // 4. 检查中断(sleep 被中断会清除中断标志)
    if (Thread::is_interrupted(THREAD, true)) {
        THROW_MSG(vmSymbols::java_lang_InterruptedException(), ...);
    }
}

sleep() 直接调用操作系统定时器,与锁、Monitor、permit 完全无关。citation:2


3. 许可机制(Permit)------park/unpark 的核心设计
  • 3.1 什么是 Permit?

每个 Java 线程在操作系统层面关联一个 permit(许可),这是一个二元标志(0 或 1):

操作 Permit 状态变化 行为
park() 1 → 0 消费许可,直接返回
park() 0 → 0 无许可,阻塞等待
unpark() 0 → 1 发放许可,若线程正在 park 则唤醒
unpark() 1 → 1 许可已存在,不累积(最多 1 个)
  • 3.2 "先 unpark 后 park" 的语义保证

这是 park/unpark 相比 wait/notify 最核心的优势:

java 复制代码
// ✅ 正确:先 unpark 再 park,不会阻塞
LockSupport.unpark(threadA); // 发放 permit(此时 threadA 还没 park)
// ... 其他代码 ...
LockSupport.park(); // 消费 permit,直接返回,不会阻塞!

// ❌ 错误:先 notify 再 wait,信号丢失
// 如果 notify 时线程还没 wait,信号完全丢失,线程永远阻塞
obj.notify(); // 此时无人等待,信号浪费
// ... 其他代码 ...
obj.wait();   // 永远等不到上面的 notify!

关键差异unpark() 的 permit 可以预先发放并缓存(最多 1 个),而 notify() 没有缓存机制,发送时无人等待则信号丢失。citation:3

  • 3.3 中断对 park 的影响
java 复制代码
Thread thread = new Thread(() -> {
    System.out.println("开始 park");
    LockSupport.park(); // 被中断后:直接返回,不抛异常!
    System.out.println("park 返回,中断状态:" + Thread.interrupted()); // true
});
thread.start();
Thread.sleep(100);
thread.interrupt(); // 中断 park 的线程

重要park() 被中断后 不抛出 InterruptedException ,只是让 park() 立即返回。中断标志位 不清除 (与 wait() 相同,与 sleep() 不同)。


4. 三种机制的中断响应差异
特性 wait() park() sleep()
中断时抛异常 InterruptedException ❌ 不抛异常 InterruptedException
中断后行为 从 wait 返回,需重新竞争锁 从 park 返回,继续执行 从 sleep 返回,继续执行
中断标志位 不清除 不清除 清除(置 false)
代码处理 catch + interrupt() 恢复 检查 Thread.interrupted() catch 即可
java 复制代码
// wait() 中断处理
try {
    synchronized (lock) { lock.wait(); }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 恢复标志位
}

// park() 中断处理(无异常!)
LockSupport.park();
if (Thread.interrupted()) { // 检查中断状态
    // 处理中断逻辑
}

// sleep() 中断处理
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // 中断标志已被清除,如需保留需手动设置
    Thread.currentThread().interrupt();
}

5. 为什么 AQS 用 LockSupport 而不是 wait/notify?
  • 5.1 AQS 的设计需求

AbstractQueuedSynchronizer(AQS)是 ReentrantLockSemaphoreCountDownLatch 的底层框架,它需要:

  1. 精确唤醒指定线程unpark(thread) 可以唤醒特定线程,而 notify() 随机唤醒。
  2. 无需持有锁:AQS 的等待队列管理不需要与某个对象的 Monitor 绑定。
  3. 先唤醒后阻塞的语义unpark 的 permit 机制避免了信号丢失。
  4. 更丰富的阻塞/唤醒控制:支持超时、截止时间、不响应中断等。
  • 5.2 AQS 的 park/unpark 使用模式
java 复制代码
// AQS.acquireQueued() 核心逻辑(简化)
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null;
                failed = false;
                return interrupted;
            }
            // ★ 阻塞前检查是否需要 park
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())  // ★ 调用 LockSupport.park()
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

// AQS.release() 核心逻辑(简化)
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);  // ★ 调用 LockSupport.unpark()
        return true;
    }
    return false;
}

AQS 使用 CLH 变体队列 管理等待线程,通过 LockSupport.park() 阻塞、LockSupport.unpark() 唤醒,完全脱离了 Object 的 Monitor 机制。citation:4


6. 生产环境避坑指南
  • 6.1 park() 不会释放锁
java 复制代码
// ❌ 错误:在同步块内 park,导致死锁
synchronized (lock) {
    LockSupport.park(); // 持有锁阻塞,其他线程无法获取锁
}

// ✅ 正确:park 前释放锁
synchronized (lock) {
    // ... 业务逻辑
}
LockSupport.park(); // 无锁阻塞
  • 6.2 park() 被虚假唤醒后需重新检查条件
java 复制代码
// ❌ 错误:park 后直接执行业务
LockSupport.park();
doWork(); // 可能被虚假唤醒,条件不满足!

// ✅ 正确:while 循环检查条件
while (!conditionMet) {
    LockSupport.park();
}
doWork();

虽然 park() 的虚假唤醒概率比 wait() 低得多,但防御性编程原则仍建议用 while 循环。

  • 6.3 unpark() 可以重复调用,但 permit 不累积
java 复制代码
LockSupport.unpark(threadA); // permit = 1
LockSupport.unpark(threadA); // permit = 1(不累积!)
LockSupport.unpark(threadA); // permit = 1(不累积!)

threadA.park(); // 消费 permit = 1,直接返回
threadA.park(); // permit = 0,永久阻塞!

注意 :多次 unpark() 不会累积多个 permit,最大为 1。

  • 6.4 parkNanos() 的精度问题
java 复制代码
// parkNanos 依赖操作系统调度,实际等待时间 ≥ 指定时间
LockSupport.parkNanos(1_000_000L); // 约 1ms,实际可能 1~2ms

如果需要高精度定时,使用 java.timeScheduledExecutorService

  • 6.5 不要在业务代码中直接使用 park/unpark
java 复制代码
// ❌ 错误:业务代码直接操作 park
LockSupport.park(); // 难以维护,逻辑晦涩

// ✅ 正确:使用高层并发工具
semaphore.acquire();      // 语义清晰
condition.await();        // 与锁绑定,安全
CountDownLatch.await();   // 明确等待事件

park/unpark 是并发框架的底层工具,业务代码应使用 ReentrantLockSemaphoreCountDownLatch 等高层抽象。

  • 6.6 Thread.interrupted() 会清除中断标志
java 复制代码
LockSupport.park();
if (Thread.interrupted()) { // ★ 调用后中断标志被清除!
    // 处理中断
}
// 后续代码无法感知中断了

如果需要保留中断状态,使用 Thread.isInterrupted()(不清除标志)。


7. 面试官追问与高分回答模板
  • 追问 1:"wait()park()sleep() 有什么区别?"

低分回答 :"wait() 释放锁,park()sleep() 不释放锁;wait() 用 notify 唤醒,park() 用 unpark 唤醒,sleep() 时间到自动醒。"(太浅,没有触及底层)

高分回答

"三者的区别要从 底层实现、锁行为、许可机制、中断响应 四个维度分析:

  1. 底层实现wait() 基于 HotSpot ObjectMonitor 的 _WaitSet 条件队列;park() 基于 Unsafe.park() 调用操作系统线程调度(Linux pthread_cond_wait);sleep() 基于操作系统定时器 os::sleep
  2. 锁行为wait() 必须持有锁且会释放锁;park()sleep() 与锁完全无关,不释放锁。
  3. 许可机制park/unpark 有独特的 permit 机制 (二元许可,最多累积 1 个),允许先 unparkpark 而不阻塞;wait/notify 没有缓存机制,先 notifywait 信号丢失。
  4. 中断响应wait()sleep() 被中断时抛 InterruptedException,但 wait() 不清除中断标志,sleep() 清除;park() 被中断时不抛异常,只是让 park() 返回,中断标志不清除。

核心记忆:

  • wait() = 协作(等别人通知,释放锁)
  • park() = 调度(直接挂起线程,无锁)
  • sleep() = 延时(自己睡一会,无锁)"
  • 追问 2:"为什么 AQS 用 LockSupport.park() 而不是 Object.wait()?"

低分回答 :"因为 park() 更灵活。"(没有触及核心原因)

高分回答

"AQS 选择 LockSupport.park() 而非 Object.wait() 有四个核心原因:

  1. 精确唤醒unpark(thread) 可以唤醒指定线程;notify() 随机唤醒一个,notifyAll() 唤醒所有产生惊群效应。AQS 需要精确唤醒队列中的后继节点。
  2. 无需持有锁wait() 必须在同步块内调用,AQS 的等待队列管理不需要与某个对象的 Monitor 绑定,使用 park() 更自由。
  3. permit 防信号丢失unpark() 可以先于 park() 调用(permit 缓存),而 notify() 发送时无人等待则信号完全丢失。这在高并发竞争场景至关重要。
  4. 功能更丰富park() 支持超时版本 parkNanos()parkUntil(),支持不响应中断;wait() 功能单一。

AQS 使用 CLH 变体队列管理等待线程,park() 阻塞、unpark() 唤醒,完全脱离了 Object Monitor 机制,这是 Java 并发包高性能的底层基础。"

  • 追问 3:"park() 的 permit 机制是什么?先 unpark 后 park 会怎样?"

高分回答

"park/unpark 的 permit 是一个 二元标志(0 或 1),每个线程独立拥有一个:

  • unpark():将 permit 从 0 设为 1。如果线程正在 park(),则唤醒它。
  • park():检查 permit,若为 1 则消费掉(设为 0)并直接返回;若为 0 则阻塞等待。

先 unpark 后 park

java 复制代码
LockSupport.unpark(threadA); // permit = 1
// ... 其他代码 ...
LockSupport.park(); // 看到 permit = 1,消费掉并立即返回,不会阻塞!

这是 park/unpark 相比 wait/notify 的核心优势。notify() 没有缓存机制,如果发送时无人 wait,信号永久丢失。

注意 :permit 最多累积 1 个,多次 unpark() 不会变成 2 或更多。"

  • 追问 4:"park() 被中断时会抛异常吗?和 wait()sleep() 有什么区别?"

高分回答

"park() 被中断时 不抛出 InterruptedException,这是三者最大的差异:

方法 中断时抛异常 中断标志位
wait() ✅ 抛 InterruptedException 不清除
sleep() ✅ 抛 InterruptedException 清除(置 false)
park() ❌ 不抛异常,只是让 park() 返回 不清除

park() 的设计哲学是'静默处理':中断只是让阻塞的线程有机会检查状态并决定是否退出。AQS 正是利用这一特性,在 parkAndCheckInterrupt() 中检测中断并记录,但不立即响应,而是等到获取锁成功后再补发中断。

代码示例:

java 复制代码
LockSupport.park();
if (Thread.interrupted()) { // 检查中断状态(注意:interrupted() 会清除标志!)
    // 处理中断逻辑
}
```"
  • 追问 5:"park()sleep() 都不释放锁,它们有什么区别?"

高分回答

"虽然两者都不释放锁,但底层机制和用途完全不同:

  1. 底层实现park() 基于操作系统线程调度(pthread_cond_wait / WaitForSingleObject),有 permit 机制;sleep() 基于操作系统定时器(nanosleep / Sleep),无 permit 机制。
  2. 唤醒方式park() 可被 unpark() 提前唤醒,也可被中断唤醒;sleep() 只能等时间到或被中断,不能被外部'提前唤醒'(除了中断)。
  3. 线程状态 :两者都进入 TIMED_WAITING(带超时版本)或 WAITING(无超时版本),但 dump 信息不同:park() 显示 parkingsleep() 显示 sleeping
  4. 使用场景park() 用于并发框架(AQS)实现阻塞/唤醒;sleep() 用于业务层的延时等待。

关键区别:park() 可以被'主动唤醒'(unpark),sleep() 只能'被动等待'(时间到)。"

  • 追问 6:"如果让你实现一个自定义锁,你会用 wait/notify 还是 LockSupport.park/unpark?为什么?"

高分回答

"我会选择 LockSupport.park/unpark,原因如下:

  1. 精确控制unpark(thread) 可以唤醒指定线程,而 notify() 随机唤醒,notifyAll() 产生惊群效应。自定义锁需要精确唤醒队列中的下一个等待者。
  2. 无锁依赖park() 不需要在同步块内调用,自定义锁可以自由设计队列结构(如 AQS 的 CLH 队列),不受 Object Monitor 的限制。
  3. permit 防丢失 :高并发下,unpark() 可以先于 park() 执行,不会丢失信号;notify() 没有这种保证。
  4. 功能丰富 :支持超时 parkNanos()、截止时间 parkUntil()、不响应中断等,满足复杂锁的需求。

实际上,Java 官方的 ReentrantLockReadWriteLockStampedLock 全部基于 AQS 实现,而 AQS 的底层就是 LockSupport.park/unpark。这是工业级验证的最佳实践。"


8. 方案选型速查表
业务场景 推荐方法 核心理由
生产者-消费者协作 wait() / notify() 释放锁,与 synchronized 配合
自定义同步器/锁 LockSupport.park/unpark AQS 底层,精确唤醒,无锁依赖
延时执行、定时轮询 Thread.sleep() 简单直接,无需额外机制
需要超时控制的阻塞 parkNanos() / wait(long) 超时自动唤醒
需要精确唤醒指定线程 LockSupport.unpark() 唯一支持指定线程唤醒
不持有锁时的线程暂停 sleep() / park() 无需锁,不会抛异常
业务代码线程协作 ReentrantLock / Semaphore 语义清晰,避免直接操作 park

💡 面试官想要的满分总结

wait()park()sleep() 的本质差异不是"释不释放锁",而是 设计目的和底层机制 的根本不同:

wait()对象协作 的工具,底层基于 ObjectMonitor 的 _WaitSet 条件队列,必须持有锁且会释放锁,被唤醒后需重新竞争锁。它解决的是"我等待某个条件,条件满足后别人通知我"的问题。

park()线程调度 的工具,底层基于 Unsafe.park() 调用操作系统线程调度器,与锁完全无关。它的核心创新是 permit 机制 ------二元许可允许先 unparkpark 而不阻塞,这是 wait/notify 无法做到的。AQS、Semaphore、CountDownLatch 全部基于 park/unpark 实现。

sleep()定时等待 的工具,底层基于操作系统定时器,与锁无关,时间到自动恢复。它解决的是"我自己暂停一段时间"的问题,不能被外部提前唤醒(除中断外)。

生产环境中的选型原则:

  1. 线程协作wait/notify(简单场景)或 Condition.await/signal(复杂场景)
  2. 自定义同步器LockSupport.park/unpark(AQS 标准做法)
  3. 延时执行Thread.sleep()ScheduledExecutorService
  4. 业务代码 → 绝不直接使用 park/unpark,用高层并发工具

面试中能讲清楚 permit 机制ObjectMonitor 源码AQS 为什么选 park,就已经超越了绝大多数候选人。


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯