侧边栏壁纸
博主头像
牧云

怀璧慎显,博识谨言。

  • 累计撰写 200 篇文章
  • 累计创建 19 个标签
  • 累计收到 8 条评论

目 录CONTENT

文章目录

深入理解 Java 线程生命周期:状态、CPU 关系与资源消耗

秋之牧云
2026-05-13 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

在 Java 并发编程的面试与实战中,线程生命周期是一个高频考点。很多开发者能背诵出 NEWRUNNABLE 等状态,但在面对“线程挂起消耗什么资源”、“BLOCKED 和 WAITING 有什么区别”等深层问题时往往语焉不详。

本文将从 状态定义CPU 交互资源消耗 以及 内存分配 四个维度,彻底拆解 Java 线程的生命周期。


一、 Java 线程的 6 种状态全景图

Java 线程的生命周期由 java.lang.Thread.State 枚举定义。理解这些状态的关键在于区分 JVM 视角操作系统视角

1. 状态详解

状态英文标识核心特征CPU 占用
新建NEW线程对象已创建,但未调用 start()
可运行RUNNABLE包含 OS 层面的 Ready(等待调度)和 Running(正在执行)。可能占用
阻塞BLOCKED等待获取 synchronized 监视器锁。
无限期等待WAITING等待其他线程显式通知(如 notify)。
超时等待TIMED_WAITING等待特定时间或通知(如 sleep)。
终止TERMINATED线程执行结束或异常退出。

2. 状态转换流程图

stateDiagram-v2
    direction TB
    NEW --> RUNNABLE : start()
    
    RUNNABLE --> BLOCKED : 竞争 synchronized 锁失败
    BLOCKED --> RUNNABLE : 获取到锁
    
    RUNNABLE --> WAITING : wait() / join() / park()
    WAITING --> BLOCKED : 被 notify() 唤醒,重新竞争锁
    BLOCKED --> RUNNABLE : 获取到锁
    
    RUNNABLE --> TIMED_WAITING : sleep() / wait(t) / join(t)
    TIMED_WAITING --> RUNNABLE : 超时 / 被通知 / 中断
    
    RUNNABLE --> TERMINATED : run() 执行完毕

注意:从 WAITING 被唤醒后,线程通常不会直接变为 RUNNABLE,而是先进入 BLOCKED 状态去竞争锁,获取锁成功后才转为 RUNNABLE


二、 线程状态与 CPU 的真实关系

很多初学者误以为 RUNNABLE 就是正在占用 CPU,其实不然。

1. 唯一可能占用 CPU 的状态:RUNNABLE

Java 中的 RUNNABLE 是一个复合状态:

  • Running:线程获得了 CPU 时间片,正在执行指令。
  • Ready:线程具备执行资格,正在排队等待 CPU 调度。

结论:只有处于 RUNNABLE 且被操作系统调度器选中的线程,才会真正消耗 CPU 计算能力。其他所有状态(BLOCKED, WAITING 等)均不占用 CPU 时间片

2. 为什么需要非运行状态?

如果所有线程都处于 RUNNABLE,CPU 将在频繁的上下文切换中耗尽性能。通过让线程进入 WAITINGBLOCKED 状态,可以主动让出 CPU,使系统能够高效处理更多并发任务。


三、 “挂起”的线程到底在干什么?

日常所说的线程“挂起”,在 Java 中通常对应 BLOCKEDWAITINGTIMED_WAITING。它们虽然都不跑代码,但等待的资源完全不同。

1. BLOCKED:在门口“抢钥匙”

  • 场景:多个线程竞争同一个 synchronized 锁。
  • 行为:线程位于对象监视器的 Entry List(入口队列)中。
  • 恢复:当持有锁的线程释放锁,JVM 从 Entry List 中唤醒一个线程去竞争锁 。

2. WAITING:在屋里“睡觉等叫”

  • 场景:调用 Object.wait()LockSupport.park()
  • 行为:线程位于对象监视器的 Wait Set(等待集)中。关键点:进入此状态前,线程会释放已持有的锁
  • 恢复:必须由其他线程调用 notify()/unpark() 显式唤醒 。

3. TIMED_WAITING:定闹钟“睡觉”

  • 场景:调用 Thread.sleep()wait(timeout)
  • 行为:线程休眠指定时间。
    • sleep()不释放锁,仅暂停执行。
    • wait(timeout)释放锁,进入等待集并启动计时器 。
  • 恢复:时间到期自动醒来,或被提前通知。

四、 非运行状态线程消耗什么资源?

这是一个常见的误区:线程不跑代码,就不消耗资源吗?
答案是:不消耗 CPU,但严重消耗内存!

1. 内存消耗(主要开销)

只要线程未终止(TERMINATED),无论其处于何种状态,以下内存都不会释放:

  • 虚拟机栈(Stack):每个线程默认分配约 1MB 的栈空间(可通过 -Xss 调整)。这是线程最大的内存开销,用于存储局部变量、方法调用链等 。
  • 原生线程结构:操作系统内核为每个线程维护的控制块(如 Linux 的 task_struct),属于堆外内存。
  • Thread 对象:堆内存中的 java.lang.Thread 实例。

风险:如果系统中存在成千上万个处于 WAITING 状态的线程,即使它们不占 CPU,也可能因耗尽本地内存导致 OutOfMemoryError: unable to create new native thread

2. 上下文切换开销

当大量线程在 BLOCKEDRUNNABLE 之间频繁切换时,会导致 CPU 缓存失效,增加调度器的负担,从而降低系统整体吞吐量 。


五、 线程内存分配在哪里?

理解线程内存分布有助于排查 OOM 问题。

内存区域分配位置是否受 GC 管理说明
线程栈 (Stack)堆外 (Native)每个线程独占,存储栈帧。OOM 常见源头 。
Thread 对象堆内 (Heap)存放线程元数据(名称、状态等)。
TLAB堆内 (Heap)Eden 区中线程私有的对象分配缓冲区,加速对象创建 。
程序计数器堆外 (Native)极小,记录当前执行的字节码行号。

六、 总结与最佳实践

  1. 状态本质
    • BLOCKED 是被动等待锁。
    • WAITING 是主动让权等通知。
    • TIMED_WAITING 是带超时的等待。
  2. 资源视角:线程是“CPU 友好”但“内存敏感”的资源。非运行状态线程不占 CPU,但持续占用栈内存。
  3. 开发建议
    • 使用线程池:严格限制最大线程数,避免无限创建线程导致内存溢出。
    • 减少锁竞争:优化同步代码块,减少线程进入 BLOCKED 状态的频率。
    • 合理超时:在使用 waitjoin 时,尽量设置超时时间,防止线程永久挂起。
0

评论区