小白学编程之——深入理解Java线程的完整生命周期
一、线程是什么?
如果把程序比作一个工厂,线程就是工厂里的工人。每个工人独立完成一项任务,多个工人协作可以提升效率。但工人(线程)的生命周期并非“一直工作”,而是会根据任务需求和资源分配不断变化状态。理解线程的“一生”,是掌握多线程编程的关键。
二、线程的六大生命周期状态
以Java为例,线程的生命周期分为6种状态,官方定义在Thread.State
枚举类中:
1. NEW(新建状态)
- 定义:通过
new Thread()
创建线程对象,但未调用start()
方法。 - 类比:就像刚入职的新员工,工位和电脑已准备好,但还没开始工作。
- 代码示例:
Thread thread = new Thread(() -> System.out.println("Hello")); System.out.println(thread.getState()); // 输出 NEW
2. RUNNABLE(可运行/运行状态)
- 定义:调用
start()
后进入该状态,包含两个子状态:- READY(就绪):等待CPU分配时间片。
- RUNNING(运行):正在执行任务。
- 注意:Java将就绪和运行合并为RUNNABLE,简化了状态管理。
3. BLOCKED(阻塞状态)
- 触发条件:线程试图获取被其他线程占用的同步锁(如
synchronized
代码块)。 - 类比:员工A需要打印机,但打印机正被员工B使用,A只能等待。
4. WAITING(无限等待)
- 触发方法:调用无超时的
wait()
、join()
或LockSupport.park()
。 - 特点:必须等待其他线程唤醒(如
notify()
或目标线程终止)。
5. TIMED_WAITING(超时等待)
- 触发方法:调用带时间参数的
sleep(1000)
、wait(500)
等。 - 区别:若超时后未被唤醒,线程会自动恢复至RUNNABLE。
6. TERMINATED(终止状态)
- 触发条件:
run()
方法执行完毕或抛出未捕获异常。 - 注意:终止的线程无法再次启动,必须创建新对象。
三、状态转换的“人生轨迹”
1. 从NEW到RUNNABLE
调用start()
后,线程进入就绪队列,等待CPU调度:
thread.start(); // 状态变为 RUNNABLE
2. RUNNABLE ↔ BLOCKED
- 进入BLOCKED:尝试获取已被占用的锁。
- 回到RUNNABLE:成功获得锁时。
3. RUNNABLE ↔ WAITING/TIMED_WAITING
- 进入等待:调用
wait()
、join()
或sleep()
。 - 恢复运行:被唤醒(
notify()
)或超时结束。
4. TERMINATED
线程任务完成后的最终归宿,无法逆转。
四、控制线程的“魔法方法”
方法 | 作用 | 状态影响 |
---|---|---|
start() | 启动线程 | NEW → RUNNABLE |
sleep(long) | 主动休眠指定时间 | RUNNABLE → TIMED_WAITING |
join() | 等待目标线程终止 | 当前线程进入WAITING |
interrupt() | 中断线程(需配合异常处理) | 唤醒WAITING/TIMED_WAITING |
yield() | 让出CPU时间片(不保证立即切换) | RUNNABLE → RUNNABLE |
五、线程池如何“续命”线程?
传统线程频繁创建销毁开销大,线程池通过复用线程优化生命周期:
- 核心线程:空闲时不会销毁,长期存活。
- 任务队列:任务排队等待空闲线程。
- 非核心线程:任务激增时临时创建,空闲超时后回收。
六、避坑指南(常见问题)
- 死锁:两线程互相等待对方释放锁。
解决:按固定顺序获取锁,或使用tryLock()
。 - 虚假唤醒:
wait()
可能无故返回,需用循环检查条件。 - 资源泄漏:未正确关闭线程池或I/O连接。
七、总结
理解线程生命周期就像掌握工人的工作流程:何时上岗(NEW)、何时休息(WAITING)、何时协作(BLOCKED)。通过合理使用同步机制(如synchronized
、Lock
)和线程池,可以让多线程程序既高效又稳定。