一文搞懂线程状态转换与 Java 内存模型(内含通俗解释)

以下是一篇尽量通俗、结构清晰的文章,帮助你真正搞懂:

Java 线程的 6 种状态 + 状态转换图
以及 Java 内存模型(JMM)最核心的几件事

一、线程的 6 种状态(Java Thread.State 枚举)

Java 线程在 JVM 层面一共有这 6 种状态:

状态(英文)中文名称是否占用 CPU是否持有锁通俗解释
NEW新建刚 new Thread() 出来,还没调用 start(),相当于一张还没发出去的简历
RUNNABLE可运行是(有可能)可能线程正在 JVM 里“活着”,要么正在 CPU 上跑,要么在就绪队列排队等 CPU
BLOCKED阻塞(锁等待)想进 synchronized 代码块,但锁被别人拿着,像在银行排队等叫号
WAITING无限等待调用了 wait()、join()、LockSupport.park() 后进入,像睡着了等别人叫醒
TIMED_WAITING限时等待带时间的 wait(long)、sleep(long)、join(long)、parkNanos/parkUntil
TERMINATED终止run() 方法执行完毕(正常结束或异常抛出未捕获),线程彻底死亡

最容易混淆的三个状态对比

状态对比BLOCKEDWAITINGTIMED_WAITING
等待什么等待 monitor 锁等待别人主动唤醒等待别人唤醒 或 超时自动醒
典型方法synchronized 块入口wait() / join() / park()sleep() / wait(时间) / join(时间)
持有锁情况不持有(没抢到)不持有(已经释放)不持有(已经释放)
被唤醒方式锁释放后自动竞争notify / notifyAll / unparknotify / 超时 / unpark

二、线程状态转换图(最经典的 7 条转换路径)

          new Thread() 
               ↓
             NEW
               ↓  start()
          RUNNABLE  ←──────────────┐
         /     ↑      \             │
        /      │       \            │ CPU 时间片用完 / yield()
运行中 ←───── 就绪     等待锁       │
        \      │       /            │
         \     ↓      /             │
      BLOCKED  ←──── WAITING  ←── TIMED_WAITING
               ↑        ↑             ↑
               │        │             │
         notify/notifyAll  wait()   sleep(时间)/wait(时间)
               │        │             │
               └────────┴─────────────┘
                            ↓
                       TERMINATED

最常考的几条路径解释(通俗版)

  1. NEW → RUNNABLE
    调用 start(),线程进入就绪队列排队等 CPU(不是立刻执行)
  2. RUNNABLE → TERMINATED
    run() 方法正常结束 或 抛出未捕获异常
  3. RUNNABLE → BLOCKED
    线程想进 synchronized 同步块,但发现锁被别人拿着 → 进入阻塞队列
  4. RUNNABLE → WAITING
  • obj.wait()(必须先持有锁)
  • thread.join()(等待目标线程结束)
  • LockSupport.park()
  1. RUNNABLE → TIMED_WAITING
  • Thread.sleep(毫秒)(最常见,不释放锁)
  • obj.wait(毫秒)
  • thread.join(毫秒)
  • LockSupport.parkNanos() / parkUntil()
  1. WAITING / TIMED_WAITING → RUNNABLE
  • notify / notifyAll 唤醒(但不一定马上拿到锁)
  • unpark 唤醒
  • 等待时间到(自动唤醒)
  1. BLOCKED → RUNNABLE
    锁被释放后,JVM 从阻塞队列里挑一个线程去竞争锁(不保证公平)

三、Java 内存模型(JMM)最核心的几件事(通俗版)

JMM 主要解决两个问题:

  1. 可见性(一个线程改了变量,另一个线程看不看得见)
  2. 有序性(代码写的顺序和实际执行顺序是否一致)

最核心的三个概念 + 一张图

1. 主内存 vs 工作内存

每个线程都有自己的工作内存(本地内存),里面放的是主内存变量的副本

          主内存(共享)
     age = 18   money = 1000
           ↑          ↑
   ┌───────────────┬───────────────┐
   │ 线程A工作内存 │ 线程B工作内存 │
   │ age副本=18    │ age副本=18    │
   │ money副本=1000│ money副本=1000│
   └───────────────┴───────────────┘

2. 八大原子操作(决定变量什么时候从主内存同步到工作内存)

操作含义谁发起
lock把主内存变量标记为线程独占线程
unlock把锁释放线程
read从主内存读取变量值线程
load把 read 的值放入工作内存副本线程
use线程使用工作内存中的变量副本线程
assign线程把值赋给工作内存中的变量副本线程
store把工作内存副本的值传回主内存线程
write把 store 的值写入主内存的变量线程

3. happens-before 规则(最重要!决定“能不能看到修改”)

只要满足下面任意一条,前面的写操作对后面的读操作可见

  1. 程序顺序规则:单线程内,按代码顺序执行
  2. 监视器锁规则:解锁 → 同一个锁的加锁
  3. volatile 变量规则:对 volatile 变量的写 → 后面的读
  4. 线程启动规则:Thread.start() → 线程内的任意操作
  5. 线程终止规则:线程内任意操作 → Thread.isAlive()=false / join()返回
  6. 线程中断规则:interrupt() → 检测到中断(抛异常或 isInterrupted=true)
  7. 对象终结规则:构造方法结束 → finalize() 开始
  8. 传递性:A 先于 B,B 先于 C → A 先于 C

最常考的几条通俗解释

  • 我对 volatile x 写了 100 → 后面任何线程读 x 都必须是 100
  • 我 synchronized 块里改了 age → 出块后别的线程进同一个锁的 synchronized 块一定能看到
  • 我调用 t.start() → t 线程里面的代码一定能看到 start() 之前的变量值

总结一句话口诀

线程状态:NEW → start → RUNNABLE ↔ BLOCKED / WAITING / TIMED_WAITING → TERMINATED
内存模型:每个线程有自己的工作内存 + 主内存,要想让别人看到修改,必须走 happens-before 桥梁(volatile、synchronized、start/join 等)

如果你现在能画出线程状态转换图 + 说出 volatile 为什么能保证可见性 + 举例 happens-before 的 3 个最常用规则,
那么这篇内容你就已经掌握 80% 了。

需要我再深入讲解哪一块?
比如:

  • volatile 底层实现(内存屏障 + Lock 前缀)
  • synchronized 升级过程(偏向锁→轻量级锁→重量级锁)
  • JMM 对 double/long 的非原子性处理
  • ThreadLocal 与内存泄漏

告诉我,我继续给你展开~

文章已创建 3958

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部