找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

708

积分

0

好友

92

主题
发表于 14 小时前 | 查看: 0| 回复: 0

在 Java 并发编程中,线程的暂停、唤醒与状态切换是核心知识点。sleepwait-notifypark-unparkyield 这四种方法常常被混淆,因为它们都能影响线程的运行。但它们的底层机制、使用场景和设计初衷其实差异显著。厘清这些区别,不仅能帮助我们深刻理解 Java 线程的状态转换,更能让我们在实战中编写出更健壮、高效的并发代码。

Java的线程状态

要理解线程状态如何转换,首先得清楚 Java 线程有哪些状态。根据 Thread.State 枚举,线程状态共分为六种:

  • NEW:尚未启动的线程状态,即线程对象刚被创建。
  • RUNNABLE:可运行状态。在 JVM 层面,它包含了操作系统线程的 READY(就绪)和 RUNNING(运行中)两种状态。
  • BLOCKED:阻塞状态,线程在等待一个监视器锁(例如等待进入 synchronized 方法或代码块)。
  • WAITING:无限期等待状态,线程在等待另一个线程执行特定的唤醒操作(如 notifyunpark)。
  • TIMED_WAITING:具有指定等待时间的等待状态,比如调用了带超时参数的 sleepwait 方法。
  • TERMINATED:线程已执行完毕,终止状态。

Java线程状态转换基本关系图

上图展示了这些状态之间的基本转换关系。而触发这些转换的关键,正是我们今天要深入剖析的 sleepwait-notifypark-unparkyield 方法。

核心概念速览

在逐一拆解之前,我们先快速了解它们各自的核心定位:

  • 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):为指定线程提供一个许可(如果该线程尚未持有许可)。

它的最大特点是 “先unparkpark也不会永久阻塞”,并且可以精准唤醒某个特定线程,而notify()只能随机唤醒一个。

状态转换:调用park()后,线程状态从 RUNNABLE 变为 WAITING(无限期)或 TIMED_WAITING(如果使用了parkNanosparkUntil)。被unpark()、线程被中断或超时后,状态恢复为 RUNNABLE

4. Thread.yield()

yield()是一个“暗示”,它告诉线程调度器:当前线程愿意让出当前CPU的使用权。调度器可以自由选择是否忽略这个暗示。调用yield()不会导致线程阻塞,也不释放任何锁资源

状态转换:调用yield()后,线程状态始终保持 RUNNABLE,但其子状态可能会从 RUNNING 短暂切换为 READY(就绪),然后立刻又可能被调度回 RUNNING

一图流理解完整状态转换

将上述所有方法的调用与线程状态变化结合起来,我们可以得到一张更完整的线程生命周期转换图:

Java线程状态转换完整流程图

实战选型指南

理解了核心机制,我们就能根据实际场景做出精准选择:

  1. 若仅需简单的定时延迟,不涉及线程间同步协作 → 使用 Thread.sleep()
  2. 若需在传统的synchronized锁机制下,实现复杂的线程间等待/通知模型 → 使用 Object.wait()/notify()。但需注意处理虚假唤醒(通常通过在循环中检查条件)。
  3. 若需构建自定义同步组件,或需要精准控制特定线程的阻塞与唤醒 → 优先使用 LockSupport.park()/unpark()。它是现代JUC并发工具包(如ReentrantLock, CountDownLatch)的基石。
  4. 若想进行简单的线程调度优化,让低优先级线程有机会运行(调试或特定性能测试) → 可尝试 Thread.yield(),但需明白其效果不可靠,生产环境慎用。

总结与建议
掌握这四种方法的差异,关键在于抓住 “是否释放锁”“唤醒机制是否精准、灵活” 这两个核心维度。在实际的 Java 并发编程中,相较于直接使用底层的 wait-notify,更推荐使用 java.util.concurrent 包提供的高级工具类(如 LockConditionSemaphoreCountDownLatch等)。它们基于 LockSupport 等机制构建,API 更丰富、更安全,能有效规避死锁、竞态条件和虚假唤醒等经典问题,是提升开发效率和程序健壮性的最佳实践。




上一篇:Markdown排版神器分享:三合一工具助你提升技术内容创作效率
下一篇:Java Stream Gatherers 深度解析:Java 24 中间操作自定义实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 17:28 , Processed in 0.255449 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表