线程的生命周期与数量设置
初始状态(New)线程对象在编程语言层面被创建,但操作系统线程尚未创建。可运行状态(Runnable)线程已准备好,可以被调度分配到 CPU 上执行。运行状态(Running)线程实际占用 CPU 正在执行代码。休眠状态(Blocked/Waiting/TimedWaiting)等待某个事件或资源,无法执行。终止状态(Terminated)线程执行完成或异常终止,生命周期结束。
1. 线程生命周期
生命周期的概念:
- 操作系统层面:线程的“生老病死”,叫做生命周期;
- 在 Java 中,实现并发程序的核心手段是 多线程编程。Java 的线程本质上与操作系统线程一一对应,底层由操作系统调度执行。理解线程的生命周期,要能搞懂生命周期中各个节点的状态转换机制,是掌握并发编程的基础。
1.1. 通用的线程生命周期
通用的线程生命周期基本上可以用下图这个“五态模型”来描述。这五态分别是:初始状态、可运行状态、运行状态、休眠状态和终止状态。

五模态模型介绍:
|
状态 |
描述 |
|
初始状态(New) |
线程对象在编程语言层面被创建,但操作系统线程尚未创建。 |
|
可运行状态(Runnable) |
线程已准备好,可以被调度分配到 CPU 上执行。 |
|
运行状态(Running) |
线程实际占用 CPU 正在执行代码。 |
|
休眠状态(Blocked/Waiting/TimedWaiting) |
等待某个事件或资源,无法执行。 |
|
终止状态(Terminated) |
线程执行完成或异常终止,生命周期结束。 |
- 注:有的语言合并了某些状态,例如 C 的 Pthreads 合并了 “初始+可运行”,Java 合并了 “可运行+运行”。
1.2. Java中线程的生命周期
Java 语言中线程共有六种状态。
但其实在操作系统层面,Java 线程中的 BLOCKED、WAITING、TIMED_WAITING 是一种状态,即前面我们提到的休眠状态。也就是说只要 Java 线程处于这三种状态之一,那么这个线程就永远没有 CPU 的使用权。

Java线程六种状态:
|
状态 |
描述 |
|
NEW |
刚创建,尚未启动。 |
|
RUNNABLE |
正在运行或等待 CPU 时间片。 |
|
BLOCKED |
等待获取对象锁(synchronized)。 |
|
WAITING |
无时限等待另一个线程的动作。 |
|
TIMED_WAITING |
有时限等待另一个线程的动作。 |
|
TERMINATED |
线程已结束执行。 |
1.3. Java线程的状态转换
1. NEW → RUNNABLE
NEW状态:
Java 刚创建出来的 Thread 对象就是 NEW 状态。
// --------方法1:继承 Thread 对象,重写 run() 方法-----------------
// 自定义线程对象
class MyThread extends Thread {
public void run() {
// 线程需要执行的代码
......
}
}
// 创建线程对象
MyThread myThread = new MyThread();
// ----方法2:实现 Runnable 接口,重写 run() 方法,并将该实现类作为创建 Thread 对象的参数---
// 实现 Runnable 接口
class Runner implements Runnable {
@Override
public void run() {
// 线程需要执行的代码
......
}
}
// 创建线程对象
Thread thread = new Thread(new Runner());
- 通过
Thread.start()方法启动线程。 - 示例:
Thread t = new Thread(() -> { ... });
t.start();
2. RUNNABLE → BLOCKED
- 场景:竞争
synchronized资源失败。 - 唤醒条件:锁释放后获得锁。
3. RUNNABLE → WAITING
- 进入条件:
-
Object.wait()(需持有锁)Thread.join()(无超时)LockSupport.park()
- 唤醒方式:
-
notify()/notifyAll()join()的目标线程结束LockSupport.unpark(Thread)
4. RUNNABLE → TIMED_WAITING
- 进入条件:
-
Thread.sleep(time)Object.wait(time)Thread.join(time)LockSupport.parkNanos()/parkUntil()
- 唤醒方式:
-
- 时间到期
- 被中断
- 区别:
-
- TIMED_WAITING 和 WAITING 状态的区别,仅仅是触发条件多了超时参数。
5. 任意状态 → TERMINATED
- 正常执行完成或异常终止。
run()方法执行结束。- 被外部中断或发生未捕获异常。
中断机制:interrupt() 与线程终止
使用:Thread.interrupt()
- 通知线程中断,不强制终止。
- 响应方式:
抛出异常:在阻塞方法(如 sleep()、wait())中会抛出 InterruptedException
主动检测:通过 Thread.isInterrupted() 检测中断状态
I/O 情况:
-
-
- 阻塞在
InterruptibleChannel:抛出ClosedByInterruptException - 阻塞在
Selector:立即返回
- 阻塞在
-
示例:
public class InterruptDemo {
public static void main(String[] args) {
Thread worker = new Thread(new Task(), "WorkerThread");
worker.start();
// 主线程等待 3 秒后中断 worker 线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("主线程被中断");
}
System.out.println("主线程:尝试中断子线程...");
worker.interrupt(); // 发出中断信号
}
static class Task implements Runnable {
@Override
public void run() {
while (true) {
// 检查是否收到中断请求
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + ":检测到中断,准备退出...");
break;
}
try {
System.out.println(Thread.currentThread().getName() + ":正在工作...");
Thread.sleep(1000); // 可能在这里抛出 InterruptedException
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + ":sleep 中被中断!");
// 设置中断标志(因为被清除),并跳出循环
Thread.currentThread().interrupt();
break;
}
}
System.out.println(Thread.currentThread().getName() + ":已退出。");
}
}
}
线程调试技巧:
1. jstack 命令:示例输出
Thread State: 当前线程状态locked/waiting to lock: 显示锁信息- 死锁检测:明确标出死锁线程及调用栈
2. VisualVM可视化工具:可视化线程分析
- 查看线程运行状态、CPU 占用
- 手动导出线程 dump,辅助定位死锁或阻塞
2. 线程数量设置
- 多线程的目标是提升 CPU 和 I/O 的综合利用率;
- 合适的线程数量应以硬件资源为基础,结合任务特性;
- CPU 密集型任务,线程数不宜多;
- I/O 密集型任务,线程数可以远大于 CPU 核心数;
- 理论公式帮助我们建立模型,压测是最终依据。
2.1. 使用多线程的目的
目的:
- 提升程序性能。
性能的度量指标:
- 延迟(Latency):从发出请求到收到响应所需的时间,越短越好。
- 吞吐量(Throughput):单位时间内处理的请求数,越大越好。
我们使用多线程的核心目的就是:降低延迟,提高吞吐量。
2.2. 多线程的应用场景
要想“降低延迟,提高吞吐量”基本上有两个方向:
- 一个方向是优化算法;
- 另一个方向是将硬件的性能发挥到极致(和并发编程息息相关)。
前者属于算法范畴,后者则是和并发编程息息相关了。计算机主要有哪些硬件主要是两类:一个是 I/O,一个是 CPU。简言之,在并发编程领域,提升性能本质上就是提升硬件的利用率,再具体点来说,就是提升 I/O 的利用率和 CPU 的利用率。
多线程:解决 了CPU 和 I/O 设备综合利用率问题。
操作系统已经解决了磁盘和网卡的利用率问题,利用中断机制还能避免 CPU 轮询 I/O 状态,也提升了 CPU 的利用率。但是操作系统解决硬件利用率问题的对象往往是单一的硬件设备,而我们的并发程序,往往需要 CPU 和 I/O 设备相互配合工作,也就是说,我们需要解决 CPU 和 I/O 设备综合利用率的问题。关于这个综合利用率的问题,操作系统虽然没有办法完美解决,但是却给我们提供了方案,那就是:多线程。
应用示例:
单线程场景(CPU 和 I/O 交替):
- CPU 运算时,I/O 空闲
- I/O 等待时,CPU 空闲
各自利用率只有 50%
多线程场景(2 个线程交替):
- A线程做CPU运算,B线程进行I/O
- A线程做I/O,B线程做CPU运算
CPU 和 I/O 利用率都达到 100%,吞吐量提升一倍


多核 CPU 下的多线程优势:
在单核时代,多线程主要就是用来平衡 CPU 和 I/O 设备的。如果程序只有 CPU 计算,而没有 I/O 操作的话,多线程不但不会提升性能,还会使性能变得更差,原因是增加了线程切换的成本。但是在多核时代,这种纯计算型的程序也可以利用多线程来提升性能。
例如:使用 4 核 CPU 计算 1+2+...+100 亿
- 单线程:CPU 利用率只有 25%
- 4 线程:每个线程一个核,CPU 利用率 100%,响应时间缩短至 25%

2.3. 线程数量设置策略
我们的程序一般都是 CPU 计算和 I/O 操作交叉执行的,由于 I/O 设备的速度相对于 CPU 来说都很慢,所以大部分情况下,I/O 操作执行的时间相对于 CPU 计算来说都非常长,这种场景我们一般都称为 I/O 密集型计算;和 I/O 密集型计算相对的就是 CPU 密集型计算了,CPU 密集型计算大部分场景下都是纯 CPU 计算。
I/O 密集型程序和 CPU 密集型程序,计算最佳线程数的方法是不同的。
1. 对于CPU密集型计算场景:
线程数量 = CPU 核数 + 1
对于 CPU 密集型的计算场景,理论上“线程的数量 =CPU 核数”就是最合适的。不过在工程上,线程的数量一般会设置为“CPU 核数 +1”,这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,从而保证 CPU 的利用率。
2. 对于 I/O 密集型的计算场景:
最佳线程数 = CPU 核数 × [1 + (I/O 耗时 / CPU 耗时)]

如果 CPU 计算和 I/O 操作的耗时是 1:2,那多少个线程合适呢?是 3 个线程,如下图所示:CPU 在 A、B、C 三个线程之间切换,对于线程 A,当 CPU 从 B、C 切换回来时,线程 A 正好执行完 I/O 操作。这样 CPU 和 I/O 设备的利用率都达到了 100%。
工程实践建议:
1. 估算 I/O 与 CPU 的耗时比值,用于初步设定线程数;
2. 进行性能压测,关注:
- CPU 利用率
- I/O 利用率
- 吞吐量和响应时间
3. 调整线程数以达到硬件资源的最大利用
更多推荐


所有评论(0)