知识模块
☕ Java 知识模块
三、Java 并发编程
线程生命周期

线程生命周期

线程是 Java 并发编程的基础单元,理解线程的生命周期对于编写正确的并发程序至关重要。

线程的六种状态

Java 线程在生命周期中会经历六种状态,定义在 Thread.State 枚举中:

NEW → RUNNABLE ⇄ BLOCKED ⇄ WAITING ⇄ TIMED_WAITING → TERMINATED
状态说明触发条件
NEW新建状态线程对象创建,未调用 start()
RUNNABLE可运行状态调用 start(),等待 CPU 调度
BLOCKED阻塞状态等待获取锁
WAITING等待状态wait()、join()、LockSupport.park()
TIMED_WAITING计时等待sleep()、wait(long)、join(long)
TERMINATED终止状态run() 执行完毕或异常退出

状态转换详解

1. NEW → RUNNABLE

Thread thread = new Thread(() -> {
    System.out.println("线程执行");
});
// 此时线程状态为 NEW
 
thread.start(); // 状态变为 RUNNABLE

注意:只能调用一次 start(),重复调用会抛出 IllegalThreadStateException

2. RUNNABLE ⇄ BLOCKED

当线程尝试获取锁(synchronized 或 ReentrantLock)但锁被其他线程持有时:

synchronized (lock) {
    // 如果锁被占用,当前线程进入 BLOCKED 状态
    // 获取锁后,恢复为 RUNNABLE
}

3. RUNNABLE → WAITING

三种方式进入 WAITING 状态:

// 方式一:Object.wait()
synchronized (lock) {
    lock.wait(); // 释放锁,进入 WAITING
}
 
// 方式二:Thread.join()
thread.join(); // 当前线程等待 thread 执行完毕
 
// 方式三:LockSupport.park()
LockSupport.park(); // 线程暂停

唤醒方式:

  • notify() / notifyAll() 唤醒 wait()
  • 被等待的线程执行完毕唤醒 join()
  • LockSupport.unpark(thread) 唤醒 park()

4. RUNNABLE → TIMED_WAITING

带超时的等待:

Thread.sleep(1000);      // 睡眠 1 秒
lock.wait(1000);         // 等待最多 1 秒
thread.join(1000);       // 等待线程最多 1 秒
LockSupport.parkNanos(1000000000); // 纳秒级等待

超时后自动恢复为 RUNNABLE 状态。

5. → TERMINATED

线程终止的情况:

  • run() 方法正常执行完毕
  • run() 方法抛出未捕获异常
  • 调用 stop()(已废弃,不推荐)

wait/notify/notifyAll 详解

这三个方法是 Object 类的方法,必须在 synchronized 块内使用。

wait()

让当前线程释放锁并等待,直到被唤醒。

synchronized (lock) {
    while (条件不满足) {
        lock.wait(); // 释放锁,等待
    }
    // 条件满足,继续执行
}

notify() vs notifyAll()

  • notify():随机唤醒一个等待的线程
  • notifyAll():唤醒所有等待的线程

推荐使用 notifyAll(),避免"信号丢失"问题。

synchronized (lock) {
    // 改变条件
    condition = true;
    lock.notifyAll(); // 唤醒所有等待线程
}

为什么必须在 synchronized 内使用?

因为 wait() 会释放锁,如果没有先获取锁就无法释放。如果在同步代码块外调用,会抛出 IllegalMonitorStateException

状态查看示例

public class ThreadStateDemo {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        
        Thread thread = new Thread(() -> {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        System.out.println("创建后: " + thread.getState()); // NEW
        
        thread.start();
        Thread.sleep(10);
        System.out.println("start后: " + thread.getState()); // WAITING
        
        synchronized (lock) {
            lock.notifyAll();
        }
        Thread.sleep(10);
        System.out.println("唤醒后: " + thread.getState()); // TERMINATED
    }
}

常见面试题

Q1: sleep() 和 wait() 的区别?

对比项sleep()wait()
所属类ThreadObject
是否释放锁
使用位置任意synchronized 块内
唤醒方式超时自动唤醒notify/notifyAll 或超时

Q2: 为什么 wait() 要放在 while 循环中?

防止"虚假唤醒"(Spurious Wakeup):

synchronized (lock) {
    // 用 while 而不是 if
    while (!condition) {
        lock.wait();
    }
    // 条件一定满足
}

虚假唤醒可能由于操作系统底层原因发生,使用 while 可以确保唤醒后再次检查条件。

Q3: 如何优雅地停止线程?

推荐使用中断机制:

Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 执行任务
    }
    // 清理资源
});
 
thread.start();
// 需要停止时
thread.interrupt();

不推荐

  • stop():强制停止,可能导致数据不一致
  • suspend()/resume():容易死锁

Q4: 线程状态 BLOCKED 和 WAITING 的区别?

  • BLOCKED:等待获取锁,被动等待
  • WAITING:主动等待(如调用 wait()),需要其他线程唤醒

最佳实践

  1. 优先使用并发工具类:如 CountDownLatch、CyclicBarrier,而不是直接使用 wait/notify

  2. 永远在 while 循环中调用 wait()

  3. 使用 interrupt() 停止线程,而不是 stop()

  4. 避免使用 suspend()/resume()

  5. 给线程起个有意义的名字,便于调试:

Thread thread = new Thread(runnable, "用户数据处理线程");

小结

  • Java 线程有 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
  • wait/notify/notifyAll 必须在 synchronized 块内使用
  • wait() 释放锁,sleep() 不释放锁
  • 使用 while 循环检查条件,防止虚假唤醒
  • 使用 interrupt() 优雅停止线程