文章目录

Java线程的六种状态转换与sleep/wait/yield的区别

发布于 2026-04-24 07:27:16 · 浏览 8 次 · 评论 0 条

Java线程的六种状态转换与sleep/wait/yield的区别

Java多线程编程中,线程状态的控制是核心难点。理解线程的生命周期以及常用方法的区别,能够帮助我们编写出更高效、更稳定的并发程序。


1. 理解Java线程的六种状态

Java线程的生命周期被明确定义在 Thread.State 枚举中。线程在任何时刻只能处于其中一种状态。

  1. NEW(新建状态)
    线程对象被创建但尚未调用 start() 方法。此时仅仅是在堆内存中分配了资源,还没与操作系统线程关联。

  2. RUNNABLE(可运行状态)
    调用 start() 后进入该状态。它包含了两种微观情况:正在运行(Running)和就绪(Ready)。Java层面将其视为“正在运行或等待CPU调度”。

  3. BLOCKED(阻塞状态)
    线程试图获取一个被其他线程持有的监视器锁(synchronized 块)时,会进入此状态。在等待锁释放的过程中,它无法执行任何代码。

  4. WAITING(无限等待状态)
    线程在等待另一个线程执行特定动作。调用 Object.wait()Thread.join()LockSupport.park() 等无超时参数的方法会导致此状态。除非被显式唤醒,否则线程不会自动恢复。

  5. TIMED_WAITING(计时等待状态)
    与 WAITING 类似,但带有超时时间。调用 Thread.sleep()Object.wait(long timeout)Thread.join(long millis) 等带有时间参数的方法会进入此状态。时间一到或被唤醒,线程就会尝试恢复执行。

  6. TERMINATED(终止状态)
    线程执行完毕(run() 方法正常退出)或因异常意外退出。


2. 线程状态转换全景图

状态之间的转换遵循特定的规则。下面的流程图清晰展示了线程如何在不同状态间流转,特别是与锁和时间相关的路径。

stateDiagram-v2 [*] --> NEW NEW --> RUNNABLE: "start()" RUNNABLE --> TERMINATED: "执行完毕" RUNNABLE --> BLOCKED: "等待监视器锁(synchronized)" BLOCKED --> RUNNABLE: "获得锁" RUNNABLE --> WAITING: "wait(), join(),\nLockSupport.park()" WAITING --> RUNNABLE: "notify(), notifyAll()\nLockSupport.unpark(), join结束" RUNNABLE --> TIMED_WAITING: "sleep(n), wait(n),\njoin(n), parkNanos()" TIMED_WAITING --> RUNNABLE: "超时或被唤醒"

重点关注以下转换逻辑:

  • 进入阻塞:线程试图进入 synchronized 保护的临界区,但锁已被占用,必须等待锁释放。
  • 释放CPU与锁:调用 wait() 时,线程不仅释放CPU,还会释放持有的锁。
  • 保持锁:调用 sleep() 时,线程释放CPU但继续持有锁。

3. 核心方法深度解析:sleep、wait、yield

这三个方法最容易混淆,但它们的应用场景和底层机制完全不同。

3.1 Thread.sleep()

  • 定义:这是一个静态方法,作用是让当前正在执行的线程暂停指定的时间。
  • 锁机制不释放锁。如果线程当前持有 synchronized 锁,休眠期间其他线程依然无法进入该同步块。
  • 醒来方式:时间结束自动醒来;或被其他线程调用 interrupt() 打断并抛出 InterruptedException
  • 使用场景:简单的暂停,模拟耗时操作,或让出CPU给高优先级任务片刻。

3.2 Object.wait()

  • 定义:定义在 Object 类中,必须配合 synchronized 关键字使用(即必须在持有对象监视器锁的情况下调用)。
  • 锁机制释放锁。这是它和 sleep 最大的区别。调用后,线程进入 WAITING 状态,直到被 notify()notifyAll() 唤醒
  • 醒来方式:被其他线程通知;或等待超时(如果是带参的 wait(long))。
  • 使用场景:线程间通信。例如生产者-消费者模型,生产满了就 wait,让消费者消费。

3.3 Thread.yield()

  • 定义:静态方法,提示调度器当前线程愿意放弃当前使用的CPU。
  • 锁机制不释放锁。
  • 执行效果:仅仅是一个建议。调度器可能会忽略这个提示,也可能只是将线程从“运行”移回“就绪”队列,随后立刻又重新选中它。它不保证一定会发生上下文切换。
  • 使用场景:极少使用,仅在试图优化线程竞争或调试时辅助分析。

4. 方法对比总结表

为了更直观地展示区别,请参考下表。

特性 Thread.sleep() Object.wait() Thread.yield()
所属类 Thread Object Thread
锁释放 不释放 (Holds lock) 释放 (Releases lock) 不释放 (Holds lock)
唤醒条件 超时或 interrupt notify/notifyAll 或超时 取决于调度器 (随时可能再次运行)
使用位置 任何位置 必须在 synchronized 块/方法内 任何位置
异常处理 必须捕获 InterruptedException 必须捕获 InterruptedException 无需捕获异常

5. 代码实操:验证 sleep 与 wait 的锁行为

通过实际代码观察锁的释放情况。

编写测试类 LockBehaviorDemo

public class LockBehaviorDemo {
    // 共享锁对象
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 线程1:调用 sleep
        new Thread(() -> testSleep(), "Thread-Sleep").start();

        // 线程2:调用 wait
        new Thread(() -> testWait(), "Thread-Wait").start();

        // 主线程稍作等待后,尝试唤醒 Thread-Wait
        try {
            Thread.sleep(1500);
            synchronized (lock) {
                System.out.println("Main thread: 准备唤醒 Thread-Wait...");
                lock.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void testSleep() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ": 获得锁,开始休眠3秒");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": 休眠结束,释放锁");
        }
    }

    public static void testWait() {
        try {
            // 先让线程1先抢到锁
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ": 尝试获取锁...");
            System.out.println(Thread.currentThread().getName() + ": 获得锁,开始 wait");
            try {
                lock.wait(); // 释放锁并等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": 被唤醒,继续执行,即将释放锁");
        }
    }
}

运行上述代码并观察控制台输出。

预期结果分析

  1. Thread-Sleep抢到锁并打印日志,随后开始 sleep(3000)
  2. 虽然 Thread-Sleep 正在休眠,但它没有释放锁。因此,Thread-Wait 在尝试获取锁时会被 BLOCKED,无法进入 synchronized 块。
  3. 直到 3秒后 Thread-Sleep 执行完毕退出同步块,Thread-Wait 才会获得锁并打印“获得锁,开始 wait”。
  4. Thread-Wait 调用 wait() 后,立即释放锁。
  5. 主线程在 1.5秒时尝试获取锁进行 notify,此时可以成功进入,因为 Thread-Wait 已经释放了锁。

6. 关键步骤总结

在实际开发中处理线程状态时,请遵循以下最佳实践步骤:

  1. 选择正确的暂停方法:

    • 如果仅需要暂停且不关心锁释放,使用 Thread.sleep()
    • 如果需要等待特定条件且允许其他线程介入,必须使用 wait()notify() 机制。
  2. 处理中断异常:

    • 调用 sleep()wait() 时,必须在 try-catch 块中捕获 InterruptedException,并根据业务逻辑决定是终止线程还是恢复运行。
  3. 避免滥用 yield:

    • 除非你对 JVM 的线程调度机制有极深的理解,否则不要使用 yield(),它往往会导致性能下降且难以调试。
  4. 检查同步上下文:

    • 调用 wait() 前,务必确认当前线程持有该对象的监视器锁,否则会抛出 IllegalMonitorStateException

评论 (0)

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

扫一扫,手机查看

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