JUC并发编程 - 3.12 五种状态(增强版)


🚀 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

    • jconsoleVisualVM 查看,线程不会出现在活动线程列表中。

  • 解决:调用 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)方案解决。

💬 场景题(美团)
在一个高并发的接口中,你发现很多线程处于 WAITINGBLOCKED 状态,服务变慢甚至阻塞,请分析场景和解决思路。

参考答案

  • 分析:

    • 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 会自动超时唤醒。

🧠 理论理解
大型系统中,线程状态是性能瓶颈排查的重要指标。通过 jstackjconsole 等工具,可以实时查看线程状态分布,判断是否有大量线程卡在阻塞或等待状态。

🏢 企业实战理解

  • 字节跳动:内部监控平台会定时抓取线程快照,统计 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 状态细节调优视角大厂级应用背景,让你既懂理论也看清实战问题。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值