第3章
🔄 线程生命周期
深入理解线程的生命周期和状态转换,掌握线程状态的查看和监控方法
学习目标
- 深入理解线程的生命周期和状态转换
- 掌握线程状态的查看和监控方法
- 学会控制线程的状态转换
- 理解线程调度的基本原理
- 掌握线程的优雅停止方法
线程状态详解
在Java中,线程的生命周期由Thread.State枚举定义,包含6个状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。理解这些状态及其转换条件是掌握多线程编程的基础。
线程状态转换图
NEW
→
RUNNABLE
→
TERMINATED
BLOCKED
↕
RUNNABLE
↕
WAITING
TIMED_WAITING
↕
RUNNABLE
六种线程状态
NEW(新建)
线程对象已创建但尚未调用start()方法。此时线程还没有开始执行。
RUNNABLE(可运行)
线程正在Java虚拟机中执行,但可能正在等待操作系统的其他资源,如CPU时间片。
BLOCKED(阻塞)
线程被阻塞等待监视器锁,通常发生在synchronized代码块或方法中。
WAITING(等待)
线程无限期等待另一个线程执行特定操作,如调用Object.wait()或Thread.join()。
TIMED_WAITING(超时等待)
线程等待指定时间,如Thread.sleep()、Object.wait(timeout)等。
TERMINATED(终止)
线程执行完毕或因异常而终止,run()方法执行完成。
重要提示
RUNNABLE状态在Java中包含了操作系统层面的READY和RUNNING状态,这是因为Java无法精确区分线程是在等待CPU时间片还是正在执行。
状态转换条件
线程状态之间的转换有明确的触发条件,理解这些条件有助于我们更好地控制线程的行为。
主要状态转换
- NEW → RUNNABLE:调用Thread.start()方法
- RUNNABLE → BLOCKED:尝试获取synchronized锁失败
- BLOCKED → RUNNABLE:成功获取synchronized锁
- RUNNABLE → WAITING:调用Object.wait()、Thread.join()等方法
- WAITING → RUNNABLE:其他线程调用Object.notify()、notifyAll()或join的线程执行完毕
- RUNNABLE → TIMED_WAITING:调用Thread.sleep()、Object.wait(timeout)等方法
- TIMED_WAITING → RUNNABLE:超时时间到达或被其他线程唤醒
- RUNNABLE → TERMINATED:run()方法执行完毕或抛出未捕获异常
// 演示线程状态转换
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.out.println("线程开始执行");
Thread.sleep(2000); // TIMED_WAITING
System.out.println("线程执行完毕");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
System.out.println("创建后状态: " + thread.getState()); // NEW
thread.start();
System.out.println("启动后状态: " + thread.getState()); // RUNNABLE
Thread.sleep(100);
System.out.println("睡眠中状态: " + thread.getState()); // TIMED_WAITING
thread.join();
System.out.println("结束后状态: " + thread.getState()); // TERMINATED
}
}
线程调度机制
Java虚拟机采用抢占式调度模型,线程的执行顺序由操作系统的线程调度器决定。理解调度机制有助于我们编写更高效的多线程程序。
调度策略
抢占式调度
高优先级线程可以抢占低优先级线程的CPU时间,但优先级只是调度的参考,不保证执行顺序。
时间片轮转
同优先级线程采用时间片轮转方式,每个线程获得相等的CPU时间片。
公平调度
现代JVM尽量保证线程调度的公平性,避免线程饥饿现象。
注意事项
不要依赖线程优先级来控制程序逻辑,因为不同操作系统的调度策略可能不同。应该使用同步机制来确保程序的正确性。
线程监控工具
Java提供了多种工具来监控和分析线程状态,这些工具对于调试和性能优化非常重要。
常用监控工具
jstack
命令行工具,用于生成线程堆栈快照,分析线程状态和死锁问题。
jstack <pid>
jconsole
图形化监控工具,可以实时查看线程状态、内存使用情况等。
VisualVM
功能强大的可视化工具,提供线程分析、性能分析等功能。
// 程序内监控线程状态
public class ThreadMonitor {
public static void monitorThread(Thread thread) {
System.out.println("线程名称: " + thread.getName());
System.out.println("线程状态: " + thread.getState());
System.out.println("是否存活: " + thread.isAlive());
System.out.println("是否守护线程: " + thread.isDaemon());
System.out.println("线程优先级: " + thread.getPriority());
}
public static void printAllThreads() {
ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
while (rootGroup.getParent() != null) {
rootGroup = rootGroup.getParent();
}
Thread[] threads = new Thread[rootGroup.activeCount()];
rootGroup.enumerate(threads);
for (Thread thread : threads) {
if (thread != null) {
System.out.println(thread.getName() + ": " + thread.getState());
}
}
}
}
优雅停止线程
正确停止线程是多线程编程中的重要技能。Java提供了interrupt机制来实现线程的优雅停止,避免使用已废弃的stop()方法。
停止线程的方法
interrupt机制
通过调用interrupt()方法设置中断标志,线程检查标志后自行停止。
标志位控制
使用volatile布尔变量作为停止标志,线程定期检查标志状态。
资源清理
在线程停止前确保释放所有资源,如关闭文件、网络连接等。
// 使用interrupt机制优雅停止线程
public class GracefulShutdownDemo {
private static class InterruptibleTask implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
System.out.println("执行任务中...");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
System.out.println("线程被中断,开始清理资源");
} finally {
// 清理资源
cleanup();
}
}
private void cleanup() {
System.out.println("资源清理完成");
}
}
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(new InterruptibleTask());
worker.start();
// 运行3秒后停止
Thread.sleep(3000);
worker.interrupt();
// 等待线程结束
worker.join();
System.out.println("主线程结束");
}
}
最佳实践
- 永远不要使用Thread.stop()方法,它已被废弃且不安全
- 在可能阻塞的操作前检查中断状态
- 捕获InterruptedException后要恢复中断状态
- 使用finally块确保资源得到正确清理
- 为长时间运行的任务提供取消机制