在 Java 并发编程中,线程的暂停、唤醒与状态切换是核心知识点。sleep、wait-notify、park-unpark、yield 这四种方法常常被混淆,因为它们都能影响线程的运行。但它们的底层机制、使用场景和设计初衷其实差异显著。厘清这些区别,不仅能帮助我们深刻理解 Java 线程的状态转换,更能让我们在实战中编写出更健壮、高效的并发代码。
Java的线程状态
要理解线程状态如何转换,首先得清楚 Java 线程有哪些状态。根据 Thread.State 枚举,线程状态共分为六种:
- NEW:尚未启动的线程状态,即线程对象刚被创建。
- RUNNABLE:可运行状态。在 JVM 层面,它包含了操作系统线程的 READY(就绪)和 RUNNING(运行中)两种状态。
- BLOCKED:阻塞状态,线程在等待一个监视器锁(例如等待进入
synchronized 方法或代码块)。
- WAITING:无限期等待状态,线程在等待另一个线程执行特定的唤醒操作(如
notify 或 unpark)。
- TIMED_WAITING:具有指定等待时间的等待状态,比如调用了带超时参数的
sleep 或 wait 方法。
- TERMINATED:线程已执行完毕,终止状态。

上图展示了这些状态之间的基本转换关系。而触发这些转换的关键,正是我们今天要深入剖析的 sleep、wait-notify、park-unpark 和 yield 方法。
核心概念速览
在逐一拆解之前,我们先快速了解它们各自的核心定位:
Thread.sleep(long millis):Thread类的静态方法。其核心作用是让当前线程暂停执行指定时间,期间不释放已持有的锁资源。设计初衷是“简单的时间等待”。
Object.wait() / notify() / notifyAll():Object类的实例方法,必须在synchronized同步块或方法中使用。其核心是实现线程间的协作通信,wait()会释放锁,notify()/notifyAll()用于唤醒等待线程。设计初衷是“基于锁的线程同步”。
LockSupport.park() / unpark(Thread thread):java.util.concurrent.locks.LockSupport类的静态方法。可精准唤醒指定线程,是构建更高级同步工具(如 AQS)的基础。设计初衷是“更灵活的线程阻塞控制”。
Thread.yield():Thread类的静态方法。其核心作用是提示调度器,当前线程愿意放弃当前CPU时间片,重新进入就绪队列。设计初衷是“调节线程优先级,优化CPU调度”,但效果并不确定。
机制与状态转换深度解析
1. Thread.sleep()
作为Thread的静态方法,sleep()可以在任何地方调用。调用后,当前线程会暂停运行指定的毫秒数,并释放CPU资源,但不释放任何它已持有的对象锁或监视器锁。
状态转换:调用后,线程状态立即从 RUNNABLE 转变为 TIMED_WAITING。当指定的休眠时间结束后,线程状态自动恢复为 RUNNABLE,等待CPU调度。
2. Object.wait() & notify()
wait()和notify()系列方法是对象级别的,用于线程间通信。关键前提是,调用线程必须已经获得了该对象的监视器锁(即在synchronized块内)。
wait():使当前线程等待,并释放持有的对象锁,同时释放CPU。
notify() / notifyAll():随机唤醒一个/全部在该对象上等待的线程。被唤醒的线程需要重新竞争锁,获得锁后才能从wait()处继续执行。
状态转换:
- 调用
wait() 后,线程状态从 RUNNABLE 变为 WAITING(无限期等待)或 TIMED_WAITING(带超时等待)。
- 被
notify()唤醒或超时后,线程状态先变为 BLOCKED(参与锁竞争),获得锁后才回到 RUNNABLE。
3. LockSupport.park() & unpark()
LockSupport提供了更底层、更灵活的线程阻塞工具。它的核心概念是“许可”(permit)。
park():如果许可可用,则立即消耗该许可并返回;否则,阻塞当前线程。
unpark(Thread thread):为指定线程提供一个许可(如果该线程尚未持有许可)。
它的最大特点是 “先unpark后park也不会永久阻塞”,并且可以精准唤醒某个特定线程,而notify()只能随机唤醒一个。
状态转换:调用park()后,线程状态从 RUNNABLE 变为 WAITING(无限期)或 TIMED_WAITING(如果使用了parkNanos或parkUntil)。被unpark()、线程被中断或超时后,状态恢复为 RUNNABLE。
4. Thread.yield()
yield()是一个“暗示”,它告诉线程调度器:当前线程愿意让出当前CPU的使用权。调度器可以自由选择是否忽略这个暗示。调用yield()不会导致线程阻塞,也不释放任何锁资源。
状态转换:调用yield()后,线程状态始终保持 RUNNABLE,但其子状态可能会从 RUNNING 短暂切换为 READY(就绪),然后立刻又可能被调度回 RUNNING。
一图流理解完整状态转换
将上述所有方法的调用与线程状态变化结合起来,我们可以得到一张更完整的线程生命周期转换图:

实战选型指南
理解了核心机制,我们就能根据实际场景做出精准选择:
- 若仅需简单的定时延迟,不涉及线程间同步协作 → 使用
Thread.sleep()。
- 若需在传统的
synchronized锁机制下,实现复杂的线程间等待/通知模型 → 使用 Object.wait()/notify()。但需注意处理虚假唤醒(通常通过在循环中检查条件)。
- 若需构建自定义同步组件,或需要精准控制特定线程的阻塞与唤醒 → 优先使用
LockSupport.park()/unpark()。它是现代JUC并发工具包(如ReentrantLock, CountDownLatch)的基石。
- 若想进行简单的线程调度优化,让低优先级线程有机会运行(调试或特定性能测试) → 可尝试
Thread.yield(),但需明白其效果不可靠,生产环境慎用。
总结与建议:
掌握这四种方法的差异,关键在于抓住 “是否释放锁” 和 “唤醒机制是否精准、灵活” 这两个核心维度。在实际的 Java 并发编程中,相较于直接使用底层的 wait-notify,更推荐使用 java.util.concurrent 包提供的高级工具类(如 Lock、Condition、Semaphore、CountDownLatch等)。它们基于 LockSupport 等机制构建,API 更丰富、更安全,能有效规避死锁、竞态条件和虚假唤醒等经典问题,是提升开发效率和程序健壮性的最佳实践。
|