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

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

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

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

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

状态英文中文常叫是否占用 CPU是否持有锁最通俗的比喻
NEW新建刚 new Thread(),相当于一张写好但还没投递的简历
RUNNABLE可运行 / 运行中是(或排队)可能有线程已经“活了”,要么正在 CPU 上跑,要么在就绪队列排队等 CPU
BLOCKED锁阻塞想进 synchronized 块,但发现门锁被别人拿着,像在银行排队等叫号
WAITING无限期等待主动把自己挂起(wait、join、park),像睡着了等别人叫醒
TIMED_WAITING限时等待带闹钟的等待(sleep、wait带时间、join带时间、parkNanos)
TERMINATED终止 / 死亡run() 执行完毕(正常结束或异常抛出未捕获),线程彻底凉了

最容易混淆的三兄弟对比

项目BLOCKEDWAITINGTIMED_WAITING
在等待什么等待 monitor 锁等待别人主动唤醒等待别人唤醒 或者 时间到
典型方法进入 synchronized 块(没抢到锁)wait() / join() / LockSupport.park()sleep() / wait(时间) / join(时间) / parkNanos
进入前是否持有锁不持有(没抢到)持有 → 调用 wait 后释放持有 → sleep 不释放,wait 释放
怎么被唤醒锁被释放后自动去竞争notify / notifyAll / unparknotify / unpark / 时间到

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

                new Thread()
                    ↓
                  NEW
                    ↓  start()
               ┌───────────────┐
               │   RUNNABLE    │◄───────────────────────┐
               │ (运行 / 就绪) │                        │
               └───────┬───────┘                        │
                       │                                │
          CPU时间片用完 / yield()                       │
                       ▼                                │
       ┌───────────────┴───────────────┐                │
       │                               │                │
 ┌─────┴──────┐                 ┌──────┴──────┐         │
 │  BLOCKED   │                 │   WAITING   │◄───────┼──┐
 │  (锁等待)  │                 │ (无限等待)  │        │  │
 └─────┬──────┘                 └──────┬──────┘        │  │
       │   锁释放后自动竞争              │                │  │
       │                                 │                │  │
       ▼                                 ▼                │  │
   ┌───────────────┐             ┌───────────────┐        │  │
   │   RUNNABLE    │◄─────────── │ TIMED_WAITING │◄───────┘  │
   └───────────────┘             │  (限时等待)   │           │
                                 └───────┬───────┘           │
                                         │                   │
                                 notify / 时间到 / unpark     │
                                         ▼                   │
                                   ┌───────────────┐         │
                                   │  TERMINATED   │◄────────┘
                                   │    (死亡)     │
                                   └───────────────┘

最常考的 7 条转换路径(一句话解释)

  1. NEW → RUNNABLE
    调用 start(),线程进入就绪队列(不一定立刻执行)
  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(毫秒)
  1. WAITING / TIMED_WAITING → RUNNABLE
  • notify / notifyAll 唤醒(但不一定马上拿到锁)
  • LockSupport.unpark() 唤醒
  • 等待时间到(自动醒来)
  1. BLOCKED → RUNNABLE
    锁被释放后,JVM 从锁等待队列里挑一个线程去竞争锁(不保证公平)

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

JMM 要解决的终极两个问题:

  1. 线程A改了变量,线程B什么时候能看见?(可见性)
  2. 我写的代码顺序,CPU真的会按这个顺序执行吗?(有序性)

1. 主内存 vs 每个线程的工作内存

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

关键点:线程之间不能直接操作对方的内存,只能操作自己的工作内存副本。

2. happens-before 规则(最重要!)

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

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

最常考的三条白话版

  • 我对 volatile int 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

如果你现在能:

  1. 画出线程状态转换图
  2. 说出 volatile 为什么能保证可见性
  3. 举出 3 个最常用的 happens-before 规则

那这篇内容你就已经掌握 80% 了。

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

  • volatile 底层(内存屏障 + Lock 前缀)
  • synchronized 锁升级过程(偏向 → 轻量 → 重量)
  • 三级缓存如何解决循环依赖
  • ThreadLocal 内存泄漏原理

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

文章已创建 3958

发表回复

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

相关文章

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

返回顶部