Thread类与线程生命周期
Thread 是 Java 表示线程的核心类。学习 Thread 的重点不是记住所有 API,而是理解线程从创建、启动、运行、阻塞、等待到终止的生命周期,以及每个状态背后的触发条件。
# 1. Thread 基本用法
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
thread.start();
start() 会启动新线程,run() 只是普通方法调用。
错误示例:
Thread thread = new Thread(task);
thread.run(); // 不会启动新线程
区别:
| 方法 | 行为 |
|---|---|
start() | 请求 JVM 启动新线程,并在新线程中执行 run |
run() | 当前线程直接调用普通方法 |
# 2. 线程状态
Java 线程状态定义在 Thread.State 中。
NEW
│ start
▼
RUNNABLE
│ 获取锁失败
▼
BLOCKED
RUNNABLE
│ wait/join/park
▼
WAITING
RUNNABLE
│ sleep/wait(timeout)/join(timeout)
▼
TIMED_WAITING
RUNNABLE
│ run 方法结束或异常退出
▼
TERMINATED
状态说明:
| 状态 | 含义 |
|---|---|
NEW | 已创建但未 start |
RUNNABLE | 可运行,可能正在运行,也可能等待 CPU |
BLOCKED | 等待进入 synchronized 监视器 |
WAITING | 无限期等待其他线程动作 |
TIMED_WAITING | 带超时时间的等待 |
TERMINATED | 线程执行结束 |
# 3. RUNNABLE 不等于正在运行
Java 的 RUNNABLE 包含操作系统层面的 ready 和 running。
Java RUNNABLE
├─ 等待 CPU 调度
└─ 正在 CPU 上执行
因此线程 dump 中看到大量 RUNNABLE,不代表它们都在消耗 CPU。需要结合栈信息判断是在计算、IO、Socket 读写,还是 native 调用。
# 4. BLOCKED
BLOCKED 表示线程在等待进入 synchronized 锁。
synchronized (lock) {
// 线程 A 持有 lock
}
// 线程 B 尝试进入同一个 synchronized 块时进入 BLOCKED
示意:
线程 A 持有 monitor
│
▼
线程 B 竞争 monitor 失败 -> BLOCKED
注意:等待 ReentrantLock 通常不是 BLOCKED,线程状态可能表现为 WAITING,因为它基于 AQS/LockSupport。
# 5. WAITING 与 TIMED_WAITING
进入 WAITING 的常见方式:
Object.wait()Thread.join()LockSupport.park()
进入 TIMED_WAITING 的常见方式:
Thread.sleep(time)Object.wait(time)Thread.join(time)LockSupport.parkNanos
对比:
| 状态 | 是否有超时 | 唤醒方式 |
|---|---|---|
WAITING | 没有 | 其他线程通知或中断 |
TIMED_WAITING | 有 | 超时、通知或中断 |
# 6. sleep、wait、join
| 方法 | 所属类 | 是否释放 synchronized 锁 | 用途 |
|---|---|---|---|
sleep | Thread | 不释放 | 当前线程暂停一段时间 |
wait | Object | 释放 | 等待条件变化 |
join | Thread | 等待目标线程结束 | 线程顺序控制 |
wait 必须在持有对象锁时调用:
synchronized (lock) {
lock.wait();
}
否则会抛 IllegalMonitorStateException。
# 7. interrupt
interrupt 是线程协作式取消机制。
thread.interrupt();
它不会强行杀死线程,而是:
- 设置线程中断标记。
- 如果线程正在
sleep/wait/join,抛出InterruptedException并清除中断标记。
正确处理:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
捕获中断异常后,如果当前方法无法处理,应恢复中断标记或向上抛出。
# 8. stop、suspend、resume
这些方法已经不推荐使用:
| 方法 | 问题 |
|---|---|
stop | 可能在任意位置释放锁,破坏对象一致性 |
suspend | 暂停线程但不释放锁,容易死锁 |
resume | 与 suspend 配合容易丢失恢复信号 |
线程停止应使用中断标记、volatile 标志位、任务取消或线程池关闭机制。
# 9. uncaughtExceptionHandler
线程执行中未捕获异常会导致线程终止。
thread.setUncaughtExceptionHandler((t, e) -> {
log.error("线程异常退出: {}", t.getName(), e);
});
线程池中任务异常要特别关注,否则任务可能静默失败,或者只在 Future 中体现。
# 10. 线程命名
线程名是排查问题的重要线索。
Thread thread = new Thread(task, "order-worker-1");
线程池应通过 ThreadFactory 统一命名:
ThreadFactory factory = runnable -> new Thread(runnable, "order-pool-" + counter.incrementAndGet());
线程 dump、日志、监控中都依赖线程名定位问题。
# Tips 快问快答
Q:start() 和 run() 有什么区别?
A:start() 启动新线程,run() 只是当前线程普通方法调用。
Q:线程可以 start 两次吗?
A:不能,第二次会抛 IllegalThreadStateException。
Q:RUNNABLE 一定正在运行吗?
A:不一定,也可能只是等待 CPU 调度。
Q:BLOCKED 一定是等锁吗?
A:在 Java 线程状态里,通常表示等待进入 synchronized 监视器。
Q:sleep 会释放锁吗?
A:不会。
Q:wait 会释放锁吗?
A:会释放当前对象的监视器锁。
Q:interrupt 会强制停止线程吗? A:不会,它是协作式取消信号。
Q:捕获 InterruptedException 后应该怎么做? A:能处理就处理;不能处理时恢复中断标记或向上抛出。
Q:为什么线程要命名? A:便于日志、线程 dump 和监控定位问题。
Q:为什么不推荐 stop? A:它可能破坏共享对象一致性,造成难以修复的数据问题。