黑马博学谷-Java并发编程原理精讲 博学谷 Java并发编程原理精讲 百度网盘下载

63 阅读4分钟

线程生命周期详解
——从 NEW 到 TERMINATED 的“一生”


一、为什么要吃透“生命周期”

  1. 写并发 Bug 的 ROOT CAUSE 90% 与状态有关:死锁、活锁、饥饿、CPU 飙高、内存泄漏……
  2. 面试必问:从“线程有哪些状态”到“WAITING 和 TIMED_WAITING 区别”再到“用 jstack 如何定位死锁”,答案都在生命周期里。
  3. 排查工具(jstack、Arthas、JProfiler)的输出直接打印状态,看不懂状态 = 看不懂报告。

二、五态模型总览(JDK 8+ 源码枚举)

java.lang.Thread.State
├─ NEW
├─ RUNNABLE
├─ BLOCKED
├─ WAITING
├─ TIMED_WAITING
└─ TERMINATED

(注:TIMED_WAITING 是 WAITING 的“限时版”,面试常合并讨论,下文单列为子状态。)


三、各状态深度拆解

  1. NEW
    源码:threadStatus=0,尚未调用 start()。
    关键点:

    • 多次 start() 抛 IllegalThreadStateException。
    • 此时 OS 线程还未创建,仅 Java 对象。
      可视化:jstack 看不到,线程尚未进入 JVM 的线程调度列表。
  2. RUNNABLE
    源码:threadStatus=5,已进入 JVM,可能正在 CPU 上,也可能在就绪队列排队。
    关键点:

    • 包含“Running”和“Ready”两个子状态,但 JVM 把它们合二为一,因此 Java 层面只有一个 RUNNABLE。
    • 调用 yield() 会让出 CPU,但状态不变,仍是 RUNNABLE。
    • I/O 阻塞(BIO)在 JVM 视角仍然是 RUNNABLE,因为底层 JVM 把阻塞交给了 OS,自己不会被挂起。
      可视化:jstack 能看到“RUNNABLE”,堆栈顶多在 socketRead0、epollWait 等 native 方法。
  3. BLOCKED
    源码:threadStatus=3,等待进入 synchronized 块/方法,且尚未获得 monitor。
    关键点:

    • 只与 synchronized 有关;LockSupport.park / ReentrantLock.lock() 不会进入 BLOCKED,而是 WAITING。
    • 可形成“死锁”:线程 A BLOCKED 等待线程 B 持有的锁,B 又在等 A 的另一把锁。
      可视化:jstack 会打印
      "Thread-1" BLOCKED on <0x00000000d5c8b8a0> (a com.xxx.A)
      owned by "Thread-0"
  4. WAITING(无限期等待)
    源码:threadStatus=6,等待“某个显式动作”唤醒。
    触发 API:

    • Object.wait() / LockSupport.park()
    • Thread.join()
    • Lock.lockInterruptibly() 竞争锁失败进入 WAITING(AQS 内部)
      关键点:
    • 必须等待其他线程“显式”唤醒(notify/notifyAll、unpark、lock 释放),否则永远 WAITING。
    • 与 BLOCKED 区别:BLOCKED 等锁,WAITING 等“信号”。
      可视化:jstack 堆栈顶常见
      at java.lang.Object.wait(Native Method)
      at java.lang.Thread.join(Thread.java:1260)
  5. TIMED_WAITING(限时等待)
    源码:threadStatus=7,带超时参数的 API 都会进入。
    触发 API:

    • Thread.sleep(long)
    • Object.wait(long)
    • Thread.join(long)
    • LockSupport.parkNanos / parkUntil
    • ReentrantLock.tryLock(long, TimeUnit)
      关键点:
    • 超时自动返回,无需其他线程唤醒。
    • 使用频率最高的是 sleep,常被用作“心跳间隔”或“重试退避”。
      可视化:jstack 堆栈顶
      at java.lang.Thread.sleep(Native Method)
  6. TERMINATED
    源码:threadStatus=8,run() 执行完毕或抛出未捕获异常。
    关键点:

    • 线程对象仍在堆中,可获取 ID、Name,但无法再 start。
    • 若线程池中的任务抛异常,线程会被回收并新建替补,旧线程进入 TERMINATED。
      可视化:jstack 不再列出;Thread.getState() 返回 TERMINATED。

四、状态跃迁大图(文字版)

NEW → start() → RUNNABLE
RUNNABLE ←→ CPU 调度(yield/time slice) → RUNNABLE
RUNNABLE → 竞争 synchronized 失败 → BLOCKED
BLOCKED → 获得 monitor → RUNNABLE

RUNNABLE → wait()/join()/park() → WAITING
WAITING → notify()/notifyAll()/unpark()/lock 释放 → RUNNABLE

RUNNABLE → sleep()/wait(long)/join(long)/parkNanos → TIMED_WAITING
TIMED_WAITING → 超时/notify/unpark → RUNNABLE

RUNNABLE → run() 结束 → TERMINATED


五、易混淆三连问(面试高频)

  1. BLOCKED vs WAITING
    等锁 vs 等信号;前者由 JVM 管,后者由程序员显式控制;jstack 输出关键字不同。

  2. sleep 会释放锁吗?
    不会。sleep 只让出 CPU,不释放已持有的 synchronized 锁;因此常配合 wait/notify 使用。

  3. RUNNABLE 表示一定在 CPU 上跑吗?
    否。Java 把 OS 的就绪队列和运行中合并,因此 RUNNABLE 也可能在排队。


六、实战:用 jstack 定位“卡死”

场景:Web 应用突然不响应,CPU 不高。
步骤:

  1. jps -l 找到 PID
  2. jstack -F PID > thread.dump
  3. 搜索“BLOCKED”或“WAITING”,发现大量线程
    BLOCKED on <0x...> owned by "http-nio-8080-exec-3"
    再搜 exec-3,发现它 WAITING on redis.clients.jedis...
    结论:Redis 连接池耗尽导致同步锁竞争,形成链式阻塞。
    解决:调大 maxTotal、加超时、改用 Lettuce 异步驱动。

七、一张图总结(保存即可)

         start()
NEW --------------→ RUNNABLE ←---------┐
  │                    ↑                │
  │        synchronized竞争失败         │ yield/time slice
  │                    ↓                │
  │              BLOCKED                │
  │                    ↑                │
  │                    │ 获得锁        │
  │                    └--------┐      │
  │                             │      │
  │ wait/join/park              │ sleep/wait(long)
  │                             │      │
  ↓                             ↓      │
WAITING ←----notify/unpark---- TIMED_WAITING
  │                             │
  │                             │ 超时
  │                             │
  └------------┬----------------┘
               │
          run()结束
               ↓
         TERMINATED

八、结语

把“五态”刻进脑子,调试并发问题时你会像看彩色照片一样直观:

  • 看到 BLOCKED 就找锁;
  • 看到 WAITING 就找 notify/unpark;
  • 看到 TIMED_WAITING 先问是不是 sleep 太久;
  • 看到 RUNNABLE 但 CPU 高,就盯死循环或疯狂自旋。

吃透生命周期,才能真正“驾驭”线程,而不是被线程“玩弄”。祝你调试愉快,面试必过!