欢迎光临
我们一直在努力

【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?

📌 人工智能开发:AI Agents 开发实践

第17题:线程有几种状态,之间如何转换?

📚 回答:

  • 核心考点: Java 线程状态是 JVM 与操作系统线程映射的核心概念。大厂面试不会只问"有哪 6 种状态",而是深入考察 JVM 线程状态与操作系统线程状态的映射关系(RUNNABLE 包含 Running 和 Ready)、BLOCKED、WAITING、TIMED_WAITING 的底层实现差异(Monitor 锁 vs LockSupport.park vs 条件变量)、以及 Thread.State 枚举的设计哲学与 jstack 实战排查。面试官真正想判断的是:你是否能从 JVM 源码层面理解线程状态的本质,以及能否在生产环境中通过线程状态定位性能问题。
1. Java 线程的 6 种状态——Thread.State 枚举

Java 在 java.lang.Thread.State 中定义了 6 种线程状态,这是 JVM 对操作系统线程状态的抽象和简化:

状态枚举值说明触发条件唤醒方式
NEW Thread.State.NEW 线程已创建,未启动 new Thread() 无(只能 → RUNNABLE)
RUNNABLE Thread.State.RUNNABLE 可运行(含运行中 + 就绪等待调度) start() 无(调度器分配 CPU)
BLOCKED Thread.State.BLOCKED 阻塞等待 Monitor 锁 synchronized 竞争失败 锁被释放
WAITING Thread.State.WAITING 无限期等待,需显式唤醒 wait()/join()/LockSupport.park() notify()/notifyAll()/unpark()
TIMED_WAITING Thread.State.TIMED_WAITING 限期等待,超时自动唤醒 sleep()/wait(timeout)/join(timeout)/parkNanos()/parkUntil() 超时到期或被唤醒
TERMINATED Thread.State.TERMINATED 线程执行完毕 run() 正常结束或异常退出 无(终态)

关键认知:RUNNABLE 在 JVM 层面合并了操作系统的 Running(运行中)和 Ready(就绪等待调度)两种状态。JVM 不区分"正在执行"和"等待 CPU",统一视为可运行。

2. 线程状态转换全链路——从源码理解转换时机

┌─────────────────────────────────────────────────────────┐
│ 线程状态转换图 │
├─────────────────────────────────────────────────────────┤
│ │
│ NEW │
│ │ │
│ │ start() │
│ ▼ │
│ RUNNABLE ◄─────────────────────────────────────┐ │
│ │ │ │
│ │ 获取锁失败 │ │
│ ▼ │ │
│ BLOCKED ──► 获取锁成功 ──►──────────────────────┘ │
│ │ │ │
│ │ wait() / join() / park() │ │
│ ▼ │ │
│ WAITING ──► notify() / notifyAll() / unpark() ──┘ │
│ │ │ │
│ │ sleep(timeout) / wait(timeout) / parkNanos() │ │
│ ▼ │ │
│ TIMED_WAITING ──► 超时到期 / 被唤醒 ───────────────┘ │
│ │ │
│ │ run() 结束或异常 │
│ ▼ │
│ TERMINATED │
│ │
└─────────────────────────────────────────────────────────┘

  • 2.1 NEW → RUNNABLE:线程启动

    Thread t = new Thread(() -> { /* 任务 */ });
    // t.getState() == NEW
    t.start();
    // t.getState() == RUNNABLE(可能立即被调度执行,也可能在就绪队列等待)

    源码层面:Thread.start() 调用 native start0(),由 JVM 创建操作系统线程并调用 run()。

  • 2.2 RUNNABLE → BLOCKED:Monitor 锁竞争失败

    Object lock = new Object();

    Thread t = new Thread(() -> {
    synchronized (lock) { // 如果 lock 已被其他线程持有
    // 当前线程从 RUNNABLE → BLOCKED
    // 进入 Monitor 的 _EntryList,等待锁释放
    }
    });

    底层实现:monitorenter 指令调用 ObjectMonitor::enter,如果 _owner 不为空且不是当前线程,线程进入 _EntryList 或 _cxq(竞争队列),状态变为 BLOCKED。

  • 2.3 BLOCKED → RUNNABLE:获取 Monitor 锁

    synchronized (lock) {
    // 锁持有者执行完毕,释放锁
    // JVM 调用 ObjectMonitor::exit
    // 从 _EntryList/_cxq 中唤醒一个线程
    // 被唤醒线程状态从 BLOCKED → RUNNABLE
    }

  • 2.4 RUNNABLE → WAITING:主动放弃锁并等待

    synchronized (lock) {
    lock.wait(); // 当前线程释放锁,进入 _WaitSet,状态 → WAITING
    }

    底层实现:Object.wait() 调用 ObjectMonitor::wait,线程被封装为 ObjectWaiter 节点加入 _WaitSet,释放 _owner 并 park 阻塞。

    WAITING 的三种触发方式对比:

    方法是否释放锁等待对象唤醒方式使用场景
    Object.wait() ✅ 释放 Monitor 锁 对象的 Monitor notify()/notifyAll() 生产者-消费者
    Thread.join() ❌ 不释放(无锁可释) 目标线程 目标线程执行完毕 等待子线程完成
    LockSupport.park() ❌ 不释放(与锁无关) 当前线程 LockSupport.unpark(thread) AQS 框架阻塞
  • 2.5 WAITING → RUNNABLE:被显式唤醒

    synchronized (lock) {
    lock.notify(); // 唤醒 _WaitSet 中的一个线程
    // 被唤醒线程从 WAITING → BLOCKED(需重新竞争锁)→ RUNNABLE
    }

    关键细节:notify() 唤醒后线程不是直接进入 RUNNABLE,而是先进入 BLOCKED 状态重新竞争锁!这是面试常见陷阱。

  • 2.6 RUNNABLE → TIMED_WAITING:限时等待

    // 方式1:Thread.sleep(不释放锁)
    Thread.sleep(1000); // 状态 → TIMED_WAITING,超时后 → RUNNABLE

    // 方式2:Object.wait(timeout)(释放 Monitor 锁)
    synchronized (lock) {
    lock.wait(5000); // 状态 → TIMED_WAITING,超时或被 notify → BLOCKED → RUNNABLE
    }

    // 方式3:Thread.join(timeout)(等待目标线程)
    t.join(3000); // 状态 → TIMED_WAITING,超时或 t 结束 → RUNNABLE

    // 方式4:LockSupport.parkNanos(AQS 常用)
    LockSupport.parkNanos(1000_000L); // 状态 → TIMED_WAITING,超时或被 unpark → RUNNABLE

    sleep vs wait 的核心区别:

    特性Thread.sleep()Object.wait()
    锁释放 ❌ 不释放 ✅ 释放 Monitor 锁
    调用前提 任意位置 必须在 synchronized 块内
    唤醒方式 只能超时到期 notify() 或超时
    状态恢复 直接 → RUNNABLE 先 → BLOCKED(竞争锁)→ RUNNABLE
  • 2.7 TIMED_WAITING → RUNNABLE:超时或被唤醒

    // 超时到期:JVM 定时器触发,线程直接 → RUNNABLE
    // 被唤醒:同 WAITING,先 → BLOCKED → RUNNABLE

  • 2.8 RUNNABLE → TERMINATED:线程结束

    Thread t = new Thread(() -> {
    // run() 正常执行完毕
    });
    t.start();
    t.join(); // 等待线程结束
    // t.getState() == TERMINATED

    异常结束:

    Thread t = new Thread(() -> {
    throw new RuntimeException("异常退出");
    });
    // 未捕获异常 → 调用 UncaughtExceptionHandler → TERMINATED

3. JVM 线程状态 vs 操作系统线程状态

JVM 的 6 种状态是对操作系统线程状态的抽象,映射关系如下:

JVM 状态Linux 状态Windows 状态说明
NEW 无(未创建) 无(未创建) 仅 Java 层对象
RUNNABLE R (running) / S (sleeping) Running / Ready 可能正在执行,也可能在等待调度
BLOCKED D (uninterruptible sleep) Wait 等待 Monitor 锁
WAITING S (interruptible sleep) Wait 等待条件变量
TIMED_WAITING S (interruptible sleep) Wait 限时等待
TERMINATED Z (zombie) / 已销毁 Terminated 线程已结束

关键认知:jstack 看到的 RUNNABLE 线程,在操作系统层面可能是 R(真正运行)或 S(可中断睡眠,等待 CPU 调度)。

4. 状态转换的源码级理解
  • 4.1 Thread.sleep 的底层实现

    // JVM 源码:os::sleep 方法(OpenJDK 8)
    int os::sleep(Thread* thread, jlong millis, bool interruptable) {
    ParkEvent * const slp = thread->_SleepEvent;
    // …
    if (interruptable) {
    if (os::is_interrupted(thread, true)) return OS_INTRPT;
    // 设置线程状态为 TIMED_WAITING
    ThreadBlockInVM tbivm(thread);
    slp->park(millis); // 调用 pthread_cond_timedwait
    }
    // …
    }

    Thread.sleep 最终调用操作系统条件变量的限时等待(Linux 的 pthread_cond_timedwait)。

  • 4.2 Object.wait 的底层实现

    // ObjectMonitor::wait(OpenJDK 8)
    void ObjectMonitor::wait(jlong millis, bool interruptable, TRAPS) {
    // 1. 将当前线程封装为 ObjectWaiter
    ObjectWaiter node(Self);
    node._TState = ObjectWaiter::TS_WAIT;

    // 2. 加入 _WaitSet(条件队列)
    AddWaiter(&node);

    // 3. 释放锁
    exit(true, THREAD);

    // 4. 挂起线程(park)
    if (interruptable && Thread::is_interrupted(Self, true)) {
    // 处理中断
    }
    // …
    }

  • 4.3 LockSupport.park 的底层实现

    // Unsafe_Park(OpenJDK 8)
    UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
    // 调用线程的 ParkEvent
    thread->parker()->park(isAbsolute, time);
    UNSAFE_END

    // Parker::park(os/posix/vm/os_posix.cpp)
    void Parker::park(bool isAbsolute, jlong time) {
    if (time == 0) {
    pthread_cond_wait(_cond, _mutex); // 无限等待
    } else {
    pthread_cond_timedwait(_cond, _mutex, &absTime); // 限时等待
    }
    }

    LockSupport.park 使用线程私有的 Parker 对象,通过 pthread_cond_wait/pthread_cond_timedwait 实现,与 Object.wait 的区别是不依赖 Monitor 锁。

5. 生产环境实战——jstack 线程状态分析
  • 5.1 线程状态统计

    # 查看 Java 进程中各状态线程数量
    jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c

    # 典型输出:
    # 10 java.lang.Thread.State: RUNNABLE
    # 5 java.lang.Thread.State: BLOCKED
    # 20 java.lang.Thread.State: WAITING (on object monitor)
    # 50 java.lang.Thread.State: TIMED_WAITING (sleeping)
    # 2 java.lang.Thread.State: TIMED_WAITING (parking)

  • 5.2 BLOCKED 线程分析——锁竞争排查

    jstack <pid> | grep -A 20 "java.lang.Thread.State: BLOCKED"

    # 输出示例:
    # "http-nio-8080-exec-5" #25 daemon prio=5 os_prio=0 tid=0x00007f8b4c001800 nid=0x5203 waiting for monitor entry [0x000070000c5a5000]
    # java.lang.Thread.State: BLOCKED (on object monitor)
    # at com.example.Service.process(Service.java:45)
    # – waiting to lock <0x000000076b5c7d58> (a java.lang.Object)
    # – locked <0x000000076b5c7d68> (a java.lang.Object)
    # "http-nio-8080-exec-3" #23 daemon prio=5 os_prio=0 tid=0x00007f8b4c000800 nid=0x5201 runnable [0x000070000c3a3000]
    # java.lang.Thread.State: RUNNABLE
    # at com.example.Service.process(Service.java:45)
    # – locked <0x000000076b5c7d58> (a java.lang.Object)

    分析:线程 exec-5 等待锁 0x76b5c7d58,而 exec-3 持有该锁。如果大量线程 BLOCKED 在同一锁上,说明锁粒度过粗或持有时间过长。

  • 5.3 WAITING 线程分析——死锁排查

    jstack <pid> | grep -A 30 "Found one Java-level deadlock"

    # 输出示例:
    # Found one Java-level deadlock:
    # =============================
    # "Thread-1":
    # waiting to lock monitor 0x00007f8b4c0032b8 (object 0x000000076b5c7d58, a java.lang.Object),
    # which is held by "Thread-0"
    # "Thread-0":
    # waiting to lock monitor 0x00007f8b4c003518 (object 0x000000076b5c7d68, a java.lang.Object),
    # which is held by "Thread-1"

  • 5.4 TIMED_WAITING 线程分析——连接池/超时问题

    jstack <pid> | grep -B 1 -A 15 "TIMED_WAITING"

    # 大量 TIMED_WAITING (on object monitor) → 可能是数据库连接池等待
    # 大量 TIMED_WAITING (sleeping) → 可能是定时任务或轮询
    # 大量 TIMED_WAITING (parking) → 可能是 AQS 条件等待或 Netty EventLoop

6. 常见误区澄清
误区正确理解
RUNNABLE = 正在运行 ❌ RUNNABLE 包含运行中和就绪等待调度两种状态
BLOCKED 和 WAITING 一样 ❌ BLOCKED 是竞争锁失败被动阻塞;WAITING 是主动放弃锁等待条件
sleep 释放锁 ❌ Thread.sleep 不释放任何锁;Object.wait 才释放 Monitor 锁
notify 后立即执行 ❌ notify 后线程先进入 BLOCKED 重新竞争锁,不是直接运行
线程结束后可以 start() 再次启动 ❌ TERMINATED 是终态,无法转换,再次 start() 抛 IllegalThreadStateException
interrupt 能中断 BLOCKED ❌ interrupt 只能中断 WAITING/TIMED_WAITING,BLOCKED 无法被中断
7. 面试官追问与高分回答模板
  • 追问 1:“线程有几种状态,之间如何转换?”

    • 低分回答:“6 种状态,通过 start、wait、notify、sleep 等方法转换。”(没有触及底层实现)
    • 高分回答:

      "Java 线程有 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。

      转换链路:

      • NEW → RUNNABLE:start() 启动线程;
      • RUNNABLE → BLOCKED:synchronized 竞争 Monitor 锁失败,进入 _EntryList;
      • BLOCKED → RUNNABLE:锁被释放,从 _EntryList 唤醒;
      • RUNNABLE → WAITING:wait()(释放锁进 _WaitSet)、join()(等待目标线程)、park()(线程私有条件变量);
      • WAITING → RUNNABLE:notify()/notifyAll()(注意:唤醒后先进入 BLOCKED 重新竞争锁)、unpark()、目标线程结束;
      • RUNNABLE → TIMED_WAITING:sleep()、wait(timeout)、join(timeout)、parkNanos();
      • TIMED_WAITING → RUNNABLE:超时到期或被唤醒;
      • RUNNABLE → TERMINATED:run() 正常结束或异常退出。

      关键认知:RUNNABLE 合并了操作系统的 Running 和 Ready;notify 唤醒后线程先 BLOCKED 再 RUNNABLE,不是直接运行。"

  • 追问 2:“BLOCKED 和 WAITING 有什么区别?”

    • 高分回答:

      "两者本质不同:

    • 触发原因:BLOCKED 是被动阻塞——线程想获取锁但竞争失败,被 JVM 放入 Monitor 的 _EntryList;WAITING 是主动等待——线程已持有锁,调用 wait() 主动释放锁并进入 _WaitSet。
    • 锁状态:BLOCKED 线程不持有锁;WAITING 线程在调用 wait() 前持有锁,调用后释放。
    • 唤醒方式:BLOCKED 只能等锁持有者释放锁;WAITING 需要其他线程显式 notify 或超时到期。
    • 中断响应:BLOCKED 无法被 interrupt 中断;WAITING 可以响应中断,抛出 InterruptedException。
    • 类比:BLOCKED 像排队买票(还没轮到你);WAITING 像买完票去旁边休息区坐着等叫号(已放弃位置)。"

  • 追问 3:“sleep 和 wait 的区别是什么?”

    • 高分回答:

      "sleep 和 wait 有五个核心区别:

    • 锁释放:sleep 不释放任何锁;wait 释放当前线程持有的 Monitor 锁;
    • 调用位置:sleep 任意位置可调用;wait 必须在 synchronized 块内调用(否则抛 IllegalMonitorStateException);
    • 所属类:sleep 是 Thread 的静态方法;wait 是 Object 的实例方法;
    • 唤醒方式:sleep 只能等超时到期;wait 可以被 notify 提前唤醒;
    • 状态恢复:sleep 到期后直接 → RUNNABLE;wait 被唤醒后先 → BLOCKED(重新竞争锁)→ RUNNABLE。
    • 设计哲学:sleep 是线程自我暂停,与锁无关;wait 是对象级别的条件等待,必须配合锁使用(生产者-消费者模式)。"

  • 追问 4:“为什么 wait 被唤醒后要先进入 BLOCKED 而不是直接 RUNNABLE?”

    • 高分回答:

      "这是 JVM 的安全设计:notify 唤醒线程时,锁可能仍被 notify 调用者持有(notify 在 synchronized 块内调用,退出时才释放锁)。如果线程直接 RUNNABLE,会在锁未释放时执行,破坏互斥性。

      正确流程:

    • 线程 A 调用 wait() → 释放锁 → 进入 WAITING;
    • 线程 B 获取锁 → 执行业务 → 调用 notify() → 线程 A 被标记为’待唤醒’;
    • 线程 B 退出 synchronized → 释放锁;
    • 线程 A 从 _WaitSet 移出 → 进入 _EntryList(BLOCKED)→ 竞争锁 → 获取锁 → RUNNABLE。
    • 所以 notify 只是’通知’,不是’授权’,线程必须重新竞争锁。"

  • 追问 5:“如何用 jstack 分析线程状态定位性能问题?”

    • 高分回答:

      "jstack 线程状态分析分三步:

    • 状态统计:jstack <pid> | grep 'java.lang.Thread.State' | sort | uniq -c,快速识别异常状态分布;
    • BLOCKED 分析:大量 BLOCKED 说明锁竞争激烈,需检查锁粒度和持有时间。用 grep -A 20 'BLOCKED' 找到锁对象和持有线程;
    • WAITING/TIMED_WAITING 分析:
      • WAITING (on object monitor) + at java.lang.Object.wait → 生产者-消费者等待,检查生产者是否阻塞;
      • TIMED_WAITING (sleeping) + 大量线程 → 可能是轮询或定时任务,考虑改为事件驱动;
      • TIMED_WAITING (parking) + at sun.misc.Unsafe.park → AQS 等待,检查锁竞争或条件等待;
      • TIMED_WAITING + at java.net.SocketInputStream.socketRead0 → IO 等待,检查外部服务延迟。
    • 实战案例:某系统 CPU 低但响应慢,jstack 发现 200 个线程中 150 个 TIMED_WAITING (parking) 在 HikariPool.getConnection,定位到数据库连接池耗尽。"

  • 追问 6:“线程结束后还能重新 start 吗?”

    • 高分回答:

      "不能。线程进入 TERMINATED 状态后是终态,无法转换到任何其他状态。再次调用 start() 会抛出 IllegalThreadStateException。

      原因:JVM 中线程的 eetop(操作系统线程句柄)在结束后已被释放,再次启动需要重新创建操作系统线程,而 Java 的 Thread 对象设计为一次性使用。

      如果需要复用线程,应使用线程池(ThreadPoolExecutor),线程池中的线程执行完任务后回到等待状态(WAITING/TIMED_WAITING),而非 TERMINATED,可以重复接收任务。"

8. 方案选型速查表
场景线程状态变化触发方法注意事项
线程启动 NEW → RUNNABLE start() 只能调用一次
锁竞争 RUNNABLE → BLOCKED synchronized 无法被 interrupt
条件等待 RUNNABLE → WAITING wait() 必须在 synchronized 内,释放锁
限时等待 RUNNABLE → TIMED_WAITING sleep()/wait(timeout) sleep 不释放锁
AQS 阻塞 RUNNABLE → WAITING/TIMED_WAITING LockSupport.park() 不依赖 Monitor 锁
等待子线程 RUNNABLE → WAITING join() 目标线程结束自动唤醒
线程结束 RUNNABLE → TERMINATED run() 结束 终态,不可复用

💡 面试官想要的满分总结:

Java 线程的 6 种状态是 JVM 对操作系统线程的抽象,理解它们必须抓住三个关键点:

状态本质:RUNNABLE 不是"正在运行",而是"可被调度"(包含 Running 和 Ready);BLOCKED 是被动锁竞争失败,WAITING 是主动条件等待;TERMINATED 是终态不可复用。

转换细节:notify 唤醒后线程先 BLOCKED 再 RUNNABLE(需重新竞争锁);sleep 不释放锁而 wait 释放锁;interrupt 只能中断 WAITING/TIMED_WAITING,BLOCKED 无法中断。

生产实战:jstack 是线程状态分析的利器——BLOCKED 多说明锁竞争,WAITING 多说明条件等待异常,TIMED_WAITING(parking) 多说明 AQS 锁或连接池问题。线程池复用线程避免 TERMINATED,是高性能系统的标配。

最后记住:线程状态不是孤立的 API 知识点,而是排查死锁、性能瓶颈、连接池耗尽等生产问题的核心线索。


觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯

赞(0)
未经允许不得转载:171主机测评 » 【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址