线程生命周期
线程是 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() |
|---|---|---|
| 所属类 | Thread | Object |
| 是否释放锁 | 否 | 是 |
| 使用位置 | 任意 | 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()),需要其他线程唤醒
最佳实践
-
优先使用并发工具类:如 CountDownLatch、CyclicBarrier,而不是直接使用 wait/notify
-
永远在 while 循环中调用 wait()
-
使用 interrupt() 停止线程,而不是 stop()
-
避免使用 suspend()/resume()
-
给线程起个有意义的名字,便于调试:
Thread thread = new Thread(runnable, "用户数据处理线程");小结
- Java 线程有 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
- wait/notify/notifyAll 必须在 synchronized 块内使用
- wait() 释放锁,sleep() 不释放锁
- 使用 while 循环检查条件,防止虚假唤醒
- 使用 interrupt() 优雅停止线程