🚀 JUC并发编程 - 3.12 五种状态(增强版)
这节讲的是 线程状态的转换过程,是从 操作系统 角度描述的。
线程状态示例代码
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行...");
sleep(2);
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");
🟦 1️⃣ 初始状态
定义
仅在语言层面创建了线程对象,还未与操作系统线程关联。此时 Thread 对象已创建,但尚未启动。
细节补充
-
对应 Java 中
new Thread(...)
这一步。 -
JVM 内部只是创建对象,没有实际的线程资源。
🧠 理论理解
初始状态是线程生命周期的第一步,仅仅是 Java 层面创建了 Thread 对象。此时操作系统中还没有真正的线程存在,只是 JVM 内部构造了一个对象,尚未分配任何系统资源。
-
对应状态:
Thread.State.NEW
-
特点:线程尚未与 OS 层建立联系,不会占用任何 CPU 资源。
🏢 企业实战理解
-
字节跳动:服务启动时会提前预创建一些 Thread 对象(池化),等到需要时再真正启动,以减少延迟。
-
阿里巴巴:在异步框架中,通过 Object Pool 管理线程对象的创建和复用,优化内存。
💬 面试题(阿里)
问:Java 中线程创建后为什么初始状态是 NEW
?它在 JVM 和 OS 层分别做了什么?
✅ 参考答案
-
NEW
状态表示线程对象已经在 Java 堆中创建,但还没有调用start()
方法。此时:-
JVM:仅分配了 Thread 对象,没有申请系统级线程资源。
-
OS:还没有创建对应的 native 线程,资源未占用。
-
-
一旦
start()
被调用,JVM 会向 OS 发起创建线程的系统调用,这时才会建立真正的线程。
💬 场景题(阿里)
你在开发中遇到这样一个现象:创建了一个 Thread
对象,但一直没执行,日志里什么都没打印出来。
请分析它此时的状态,以及怎么查看验证。
✅ 参考答案
-
这种情况是典型的初始状态(NEW):线程对象只是创建了,但
start()
方法没调用,所以没进入 JVM 线程调度系统。 -
验证:
-
调用
Thread.getState()
会返回NEW
。 -
用
jconsole
、VisualVM
查看,线程不会出现在活动线程列表中。
-
-
解决:调用
t.start()
进入就绪状态。
🟨 2️⃣ 可运行状态(就绪状态)
定义
线程对象已经和操作系统线程绑定,等待 CPU 调度。此状态下 线程可以被 CPU 选中,但尚未真正运行。
细节补充
-
对应
t1.start()
后的状态。 -
不保证立刻执行,CPU 会依据调度策略选择。
🧠 理论理解
线程执行了 start()
方法后,进入就绪状态。此时已经在操作系统内注册,等待被调度执行。可运行状态代表线程有资格运行,但还没有真正占用 CPU。
-
对应状态:
Thread.State.RUNNABLE
-
线程此时进入调度器的等待队列,等 CPU 空闲后会分配时间片。
🏢 企业实战理解
-
腾讯:QPS 高峰期可见成百上千线程处于就绪状态,等待 CPU 调度,借助
top -H
工具监控。 -
Google:Go 的调度器也借鉴了这种模型,Goroutine 在
runnable
队列等待执行,背后机制相似。
💬 面试题(字节跳动)
问:你知道 RUNNABLE
状态在 Java 中是否严格等同于 OS 层的运行状态吗?
✅ 参考答案
不是等同的。Java 中的 RUNNABLE
包括了 OS 层的:
-
就绪(Ready)状态:等待 CPU 调度。
-
运行(Running)状态:正在执行。
也就是说,Java 的RUNNABLE
是 OS 层 Ready + Running 的统称,JVM 不再细分。
💬 场景题(字节跳动)
你启动了多个线程同时处理任务,但发现它们处于 RUNNABLE
状态,CPU 利用率很低。为什么?怎么优化?
✅ 参考答案
-
现象:
-
在 Java 中
RUNNABLE
包括“就绪 + 运行”状态。 -
很可能线程在等待 CPU 时间片,虽是
RUNNABLE
,但并没实际运行。
-
-
原因:
-
CPU 核心数不足、线程太多、上下文切换频繁导致 CPU 空转。
-
-
优化:
-
减少线程数量 ≈ 核心数;
-
使用线程池避免无节制创建;
-
用工具(如
top -H
)观察 CPU 调度状态。
-
🟩 3️⃣ 运行状态
定义
线程获得 CPU 时间片,开始执行代码。线程开始“占用 CPU”。
重点
-
运行状态并不稳定,随时可能因为 时间片用尽 或 被调度器打断,切换回就绪状态。
-
线程上下文切换(Thread Context Switch)在这里发生。
🧠 理论理解
此状态是线程真正被调度执行的阶段。它获得了 CPU 时间片,在执行自己的任务代码。
运行状态不是永久的,线程可能随时因为时间片用完或优先级变化等被暂停。
-
在 Java 中,RUNNABLE 包含运行+就绪的合并状态。
-
线程上下文切换成本较高,涉及寄存器保存/恢复。
🏢 企业实战理解
-
字节跳动:音视频流媒体处理时,会监控运行线程占用的 CPU 核心数,防止单核压力过大。
-
阿里云:线程池配置结合 CPU 核心数调整任务分发,保障运行线程的高效执行。
💬 面试题(腾讯)
问:你怎么看线程上下文切换的开销?Java 是怎么实现线程状态切换的?
✅ 参考答案
-
线程上下文切换成本包括:
1️⃣ CPU 寄存器保存和恢复。
2️⃣ 内存页表切换。
3️⃣ JVM 层还要保存程序计数器、虚拟机栈、局部变量等。 -
在 Java 中,线程调度由操作系统控制,JVM 会维护线程的状态信息(如 PC 寄存器)来跟踪当前执行位置。
💬 场景题(腾讯)
线上业务高峰期,你发现系统响应变慢,CPU 使用率飙高,怀疑是某些线程一直占用 CPU。怎么定位哪个线程在运行?
✅ 参考答案
-
用
top -H -p <PID>
查看各线程 CPU 占用情况。 -
找到高占用的线程 ID(
LWP
),用printf "%x"
转成 16 进制。 -
再用
jstack <PID>
查看对应的线程栈,分析它在执行什么逻辑。 -
常见问题:
-
死循环;
-
大量数据处理没分片;
-
锁竞争异常。
-
🟫 4️⃣ 阻塞状态
定义
当线程执行一些会阻塞的操作(如 IO 阻塞、等待锁、调用 sleep
等),CPU 不再调度它执行,进入阻塞。
补充场景
-
Thread.sleep(2000);
-
InputStream.read()
-
等待锁(如
synchronized
)时未获取到锁。
区别点
-
可运行状态线程:只要 CPU 空闲,它就可能会被调度。
-
阻塞状态线程:即使 CPU 空闲,也不会被调度,必须等它被唤醒才会进入就绪状态。
🧠 理论理解
线程进入阻塞状态,通常是因为执行了阻塞式 API(如 I/O 操作、等待锁等),此时它暂时失去执行资格。只有在等待的事件发生后(如 I/O 完毕、锁释放),它才会重新进入就绪状态。
-
对应状态:
-
Thread.State.BLOCKED
(等待锁) -
Thread.State.WAITING/TIMED_WAITING
(等待/定时等待)
-
🏢 企业实战理解
-
美团:高并发秒杀系统中避免使用阻塞式 I/O,转向异步消息队列架构,减少线程阻塞。
-
NVIDIA:在 CUDA 编程中同样避免 GPU 线程阻塞,以提升并行执行效率。
💬 面试题(美团)
问:阻塞状态和等待状态有什么区别?阻塞 I/O 在高并发场景下会带来哪些问题?
✅ 参考答案
-
区别:
-
阻塞状态通常是线程在等待 I/O 或锁(如
synchronized
)。 -
等待状态是主动调用
wait()
、join()
等,进入等待某个条件。
-
-
问题:
-
阻塞 I/O 会导致线程大量占用资源却无法释放(如卡死在磁盘读写上)。
-
高并发下会堆积大量线程,造成线程池耗尽甚至 OOM,推荐使用异步 I/O(NIO)方案解决。
-
💬 场景题(美团)
在一个高并发的接口中,你发现很多线程处于 WAITING
或 BLOCKED
状态,服务变慢甚至阻塞,请分析场景和解决思路。
✅ 参考答案
-
分析:
-
BLOCKED
→ 等待进入synchronized
锁; -
WAITING
→ 等待某个条件(如wait()
)。
-
-
原因:
-
大对象锁(锁粒度太大);
-
线程池满载,阻塞等待资源;
-
阻塞 I/O(文件、数据库)。
-
-
解决:
-
拆分锁、加细粒度;
-
用
ReentrantLock
+tryLock()
; -
改用异步 I/O(NIO、Netty)。
-
🟥 5️⃣ 终止状态
定义
线程执行完毕(run 方法正常返回或抛出异常),生命周期结束,无法再次启动。
🧠 理论理解
线程正常执行完毕(run() 方法返回)或异常退出后,会进入终止状态。线程生命周期彻底结束,操作系统会回收相关资源。
-
对应状态:
Thread.State.TERMINATED
-
特点:终止后的线程不可再次启动(会抛出
IllegalThreadStateException
)。
🏢 企业实战理解
-
OpenAI:在大规模微服务架构中,API 请求线程设计为短生命周期,用完即结束,防止线程资源泄漏。
-
华为云:在高并发 API 网关中使用守护线程监控终止状态,避免僵尸线程。
💬 面试题(OpenAI)
问:为什么线程终止后不能复用?Thread 对象还能再启动吗?
✅ 参考答案
-
因为
Thread
内部的状态机是一次性设计:-
终止状态下,线程生命周期结束,JVM 会释放和线程关联的 native 资源。
-
这时候再次
start()
会抛出IllegalThreadStateException
。
-
-
若想“复用”,必须创建新
Thread
对象,而不是复用旧的实例。
💬 场景题(OpenAI)
一次服务崩溃后,你发现有大量线程反复创建-终止,GC 日志疯狂刷屏。请分析这个问题场景,并给出解决方案。
✅ 参考答案
-
场景:
-
创建短生命周期线程,执行完立即终止(
TERMINATED
)。 -
频繁创建新线程 → 大量内存分配 + 回收,导致 GC 压力暴涨。
-
-
解决方案:
-
用
ThreadPoolExecutor
重用线程; -
分析业务逻辑是否真的需要多线程,能否用异步任务链优化。
-
🆕 新增小节1️⃣:TIMED_WAITING(定时等待)与 WAITING(无限期等待)
虽然操作系统描述中归类为“阻塞状态”,但在 Java 层面 线程状态还细分为:
-
WAITING:无限期等待(如
join()
、wait()
没有超时时间)。 -
TIMED_WAITING:定时等待(如
sleep(1000)
、wait(1000)
)。
这两个状态虽然都“停摆”,但 TIMED_WAITING 会自动超时唤醒。
🧠 理论理解
大型系统中,线程状态是性能瓶颈排查的重要指标。通过 jstack
、jconsole
等工具,可以实时查看线程状态分布,判断是否有大量线程卡在阻塞或等待状态。
🏢 企业实战理解
-
字节跳动:内部监控平台会定时抓取线程快照,统计 BLOCKED、WAITING 的线程比例,发现死锁风险。
-
Google:生产环境中定期采集 JVM 线程堆栈快照,用于分析 CPU 消耗异常的热点。
💬 面试题(华为)
问:jstack
打印出大量 BLOCKED
线程怎么排查?生产中如何避免死锁?
✅ 参考答案
-
排查思路:
1️⃣ 找到BLOCKED
状态的线程堆栈,定位是哪个对象锁导致阻塞。
2️⃣ 查看锁资源的竞争情况,看是否存在多个线程互相等待锁资源(典型死锁)。 -
预防措施:
-
尽量减少锁的粒度,使用显式锁(如
ReentrantLock
)提供tryLock()
尝试机制。 -
建立统一加锁顺序,避免“循环等待”。
-
采用无锁/并发包(如
ConcurrentHashMap
)优化。
-
💬 场景题(华为云)
线上环境中,你发现系统突然卡死,用 jstack
看是死锁,但日志没输出任何死锁提示。你怎么快速识别是哪两个线程死锁?
✅ 参考答案
-
jstack
报告里搜索Found one Java-level deadlock:
; -
找到涉及的两个线程名和对象 ID(
<0x...>
),确认是互相等待对方释放锁; -
可配合
jconsole
线程查看,标记为红色的线程通常是死锁状态。
🆕 新增小节2️⃣:Java Thread.State 枚举
Java 提供了 Thread.State
枚举,覆盖的状态为:
状态 | 描述 |
---|---|
NEW | 初始状态 |
RUNNABLE | 可运行/运行状态(OS 视角下没细分) |
BLOCKED | 阻塞等待锁状态 |
WAITING | 无限期等待 |
TIMED_WAITING | 有限期等待 |
TERMINATED | 线程终止 |
💡 注意:Java 的 RUNNABLE 包括了 就绪 + 运行 两种操作系统状态。
🆕 新增小节3️⃣:大厂常见调优点
-
字节跳动/阿里巴巴:高并发场景下,线程池数量和任务阻塞点的监控很重要,经常会通过
jstack
查看线程的状态分布,定位是否有线程过多陷入阻塞(例如等锁)。 -
Google/NVIDIA:高性能任务队列会避免过多线程进入阻塞状态,利用 异步模型(如 Reactor) 降低线程切换开销。
✅ 总结
这一节深入解析了 线程的五种基本状态:
-
初始状态
-
可运行状态
-
运行状态
-
阻塞状态
-
终止状态
并扩展了 Java Thread.State 状态细节、调优视角 和 大厂级应用背景,让你既懂理论也看清实战问题。