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

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

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

📚 回答:

  • 核心考点 : Java 线程状态是 JVM 与操作系统线程映射的核心概念。大厂面试不会只问"有哪 6 种状态",而是深入考察 JVM 线程状态与操作系统线程状态的映射关系RUNNABLE 包含 RunningReady)、BLOCKEDWAITINGTIMED_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:线程启动

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

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

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

    java 复制代码
    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 锁

    java 复制代码
    synchronized (lock) {
        // 锁持有者执行完毕,释放锁
        // JVM 调用 ObjectMonitor::exit
        // 从 _EntryList/_cxq 中唤醒一个线程
        // 被唤醒线程状态从 BLOCKED → RUNNABLE
    }
  • 2.4 RUNNABLE → WAITING:主动放弃锁并等待

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

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

    WAITING 的三种触发方式对比

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

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

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

  • 2.6 RUNNABLE → TIMED_WAITING:限时等待

    java 复制代码
    // 方式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:超时或被唤醒

    java 复制代码
    // 超时到期:JVM 定时器触发,线程直接 → RUNNABLE
    // 被唤醒:同 WAITING,先 → BLOCKED → RUNNABLE
  • 2.8 RUNNABLE → TERMINATED:线程结束

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

    异常结束

    java 复制代码
    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 的底层实现

    cpp 复制代码
    // 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 的底层实现

    cpp 复制代码
    // 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 的底层实现

    cpp 复制代码
    // 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 线程状态统计

    bash 复制代码
    # 查看 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 线程分析------锁竞争排查

    bash 复制代码
    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 线程分析------死锁排查

    bash 复制代码
    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 线程分析------连接池/超时问题

    bash 复制代码
    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 包含运行中和就绪等待调度两种状态
BLOCKEDWAITING 一样 BLOCKED 是竞争锁失败被动阻塞;WAITING 是主动放弃锁等待条件
sleep 释放锁 Thread.sleep 不释放任何锁;Object.wait 才释放 Monitor 锁
notify 后立即执行 notify 后线程先进入 BLOCKED 重新竞争锁,不是直接运行
线程结束后可以 start() 再次启动 TERMINATED 是终态,无法转换,再次 start()IllegalThreadStateException
interrupt 能中断 BLOCKED interrupt 只能中断 WAITING/TIMED_WAITINGBLOCKED 无法被中断
7. 面试官追问与高分回答模板
  • 追问 1:"线程有几种状态,之间如何转换?"

    • 低分回答:"6 种状态,通过 start、wait、notify、sleep 等方法转换。"(没有触及底层实现)
    • 高分回答 : "Java 线程有 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。

      转换链路:

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

      关键认知:RUNNABLE 合并了操作系统的 RunningReadynotify 唤醒后线程先 BLOCKEDRUNNABLE,不是直接运行。"

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

    • 高分回答 : "两者本质不同:
      1. 触发原因BLOCKED 是被动阻塞------线程想获取锁但竞争失败,被 JVM 放入 Monitor 的 _EntryListWAITING 是主动等待------线程已持有锁,调用 wait() 主动释放锁并进入 _WaitSet
      2. 锁状态BLOCKED 线程不持有锁;WAITING 线程在调用 wait() 前持有锁,调用后释放。
      3. 唤醒方式BLOCKED 只能等锁持有者释放锁;WAITING 需要其他线程显式 notify 或超时到期。
      4. 中断响应BLOCKED 无法被 interrupt 中断;WAITING 可以响应中断,抛出 InterruptedException

      类比:BLOCKED 像排队买票(还没轮到你);WAITING 像买完票去旁边休息区坐着等叫号(已放弃位置)。"

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

    • 高分回答 : "sleepwait 有五个核心区别:
      1. 锁释放sleep 不释放任何锁;wait 释放当前线程持有的 Monitor 锁;
      2. 调用位置sleep 任意位置可调用;wait 必须在 synchronized 块内调用(否则抛 IllegalMonitorStateException);
      3. 所属类sleepThread 的静态方法;waitObject 的实例方法;
      4. 唤醒方式sleep 只能等超时到期;wait 可以被 notify 提前唤醒;
      5. 状态恢复sleep 到期后直接 → RUNNABLE;wait 被唤醒后先 → BLOCKED(重新竞争锁)→ RUNNABLE。

      设计哲学:sleep 是线程自我暂停,与锁无关;wait 是对象级别的条件等待,必须配合锁使用(生产者-消费者模式)。"

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

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

      正确流程:

      1. 线程 A 调用 wait() → 释放锁 → 进入 WAITING;
      2. 线程 B 获取锁 → 执行业务 → 调用 notify() → 线程 A 被标记为'待唤醒';
      3. 线程 B 退出 synchronized → 释放锁;
      4. 线程 A 从 _WaitSet 移出 → 进入 _EntryList(BLOCKED)→ 竞争锁 → 获取锁 → RUNNABLE。

      所以 notify 只是'通知',不是'授权',线程必须重新竞争锁。"

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

    • 高分回答 : "jstack 线程状态分析分三步:
      1. 状态统计jstack <pid> | grep 'java.lang.Thread.State' | sort | uniq -c,快速识别异常状态分布;
      2. BLOCKED 分析 :大量 BLOCKED 说明锁竞争激烈,需检查锁粒度和持有时间。用 grep -A 20 'BLOCKED' 找到锁对象和持有线程;
      3. 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 唤醒后线程先 BLOCKEDRUNNABLE(需重新竞争锁);sleep 不释放锁而 wait 释放锁;interrupt 只能中断 WAITING/TIMED_WAITINGBLOCKED 无法中断。

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

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


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

相关推荐
c++之路1 小时前
Bazel C++ 构建系列文档(三):构建第一个 C++ 项目
开发语言·c++
DIY源码阁1 小时前
JavaSwing饮品管理系统 - MySQL版
java·数据库·mysql·eclipse
二哈赛车手2 小时前
新人笔记---最终版智能体图片分析完整方案,包括一些总结于经验,以及各种优化点讲解
java·笔记·spring·ai·springboot
泡^泡2 小时前
Spring AI简单高仿DeepSeek问答页面
java·人工智能·spring
聚名网2 小时前
域名net,com,cn有区别吗?有哪些不同呢?
服务器·开发语言·php
牛油果子哥q2 小时前
STL set与map底层精讲,红黑树适配原理、有序去重特性、迭代器遍历、API实战与面试核心考点全解
开发语言·数据结构·c++·面试
foundbug9992 小时前
直流电机 PID 速度控制 MATLAB 仿真程序
开发语言·matlab
带刺的坐椅2 小时前
Solon v4.0 正式发布,高考记忆版
java·ai·solon·flow·solon-ai
yoothey3 小时前
MySQL事务机制解析 - 面试高分知识点
数据库·mysql·面试