一、为什么要先搞清楚线程状态
进入并发编程这个新分类的第一篇,先不讲锁、不讲内存模型,而是从最基础的"一个线程一生会经历哪些状态"讲起——原因很简单:jstack 抓到的线程堆栈、线上死锁排查时看到的 BLOCKED、面试时被问到的"sleep 和 wait 有什么区别",全部建立在对线程状态转换的准确理解上。如果连 BLOCKED 和 WAITING 都分不清,后面讲锁的原理、讲线程池的调度策略都会似懂非懂。
Thread.State 枚举定义了六种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。这篇要讲清楚每种状态具体代表什么、状态之间怎么转换、以及几个经典的易混淆点。

二、六种状态到底是什么
- NEW:线程对象已经被创建(
new Thread(...)),但还没有调用start()方法。这个阶段线程只是一个普通的 Java 对象,还没有被操作系统调度过。 - RUNNABLE:调用
start()之后,线程进入RUNNABLE状态。这里有一个关键点很多人会搞错——Java 的**RUNNABLE**状态同时覆盖了操作系统层面的"正在运行"和"正在等待 CPU 时间片(就绪)"这两种情况,JVM 不区分这两者,统一归为RUNNABLE。也就是说,一个线程显示为RUNNABLE,并不代表它这一刻真的占着 CPU 在跑,可能只是在等操作系统调度器分配时间片。 - BLOCKED:线程正在等待获取一个
synchronized锁(进入同步方法/同步块,或者从wait()中被唤醒后重新尝试获取锁),但锁被其他线程占用。**BLOCKED**专指因为竞争**synchronized**锁而受阻的场景,这是它和WAITING最本质的区别。 - WAITING:线程主动调用了
Object.wait()(不带超时)、Thread.join()(不带超时)或者LockSupport.park(),进入无限期等待,直到被其他线程显式唤醒(notify()/notifyAll())、被 join 的目标线程结束、或者被unpark()。 - TIMED_WAITING:和
WAITING类似,但带了超时时间——Thread.sleep(ms)、Object.wait(ms)、Thread.join(ms)、LockSupport.parkNanos(ns)都会进入这个状态,超时后会自动恢复,不需要依赖其他线程唤醒。 - TERMINATED:线程的
run()方法执行完毕(正常返回或者抛出未捕获异常),线程生命周期结束,之后无法再次start()。

三、BLOCKED 与 WAITING:最容易混淆的一对状态
这是面试里被问得最多的一个区别点,核心判断标准是触发等待的原因:
- BLOCKED:单纯因为"抢不到锁"而被动挂起,线程什么都没主动做,只是排队等
synchronized释放锁。一旦锁被释放、当前线程抢到了,会自动变回RUNNABLE,不需要任何显式唤醒代码。 - WAITING / TIMED_WAITING:线程是主动调用了
wait()、join()、park()之类的方法进入等待,等待的是某个条件被满足(比如被notify(),或者等待的线程执行结束),不是单纯在排队抢锁。
一个常见的实际场景可以帮助记住这个区别:线程 A 调用 obj.wait() 会先释放 obj 上的锁,进入 WAITING;如果之后被 notify() 唤醒,线程 A 不会直接变回 RUNNABLE,而是要先重新竞争 obj 的锁——这个"重新抢锁"的中间过程,状态就是 BLOCKED。所以同一个线程围绕一次 wait/notify 的生命周期,可能是 RUNNABLE → WAITING → BLOCKED → RUNNABLE,两种状态在真实场景里往往是接续发生的,不是二选一。

四、几个容易踩的陷阱
- 直接调用
**run()**而不是**start()**。run()只是一个普通方法调用,在当前线程里同步执行,根本不会创建新线程,线程状态也永远不会经过NEW → RUNNABLE的转换。只有调用start(),JVM 才会真正向操作系统申请一个新的执行线程。 - 对同一个线程对象调用两次
**start()**。 线程一旦启动过(进入过RUNNABLE甚至已经TERMINATED),再调用start()会直接抛出IllegalThreadStateException,因为NEW状态是"仅一次可用"的门票,状态机不允许从其他状态跳回NEW再重新start。 - 混淆
**sleep()**和**wait()**对锁的影响。Thread.sleep(ms)不会释放当前持有的任何锁,只是让当前线程暂停执行;而Object.wait()会释放调用它时所持有的那个对象的锁,这也是为什么wait()必须在synchronized块内调用(要释放的锁必须是已经持有的锁),否则会抛IllegalMonitorStateException。 **interrupt()**不会强制终止线程。 调用thread.interrupt()只是给目标线程设置一个"中断标志位",如果线程正阻塞在sleep/wait/join上,会收到一个InterruptedException并被唤醒;但如果线程在执行普通计算代码,interrupt()不会有任何自动效果,需要代码自己定期检查Thread.interrupted()或isInterrupted()来响应中断。

五、run() 与 start() 的本质区别
很多初学者以为 run() 和 start() 差别不大,只是"名字不同",实际上这是两条完全不同的执行路径:
Thread t = new Thread(() -> System.out.println("当前线程:" + Thread.currentThread().getName()));
t.run(); // 打印 main(在调用者所在的线程里同步执行,没有新线程参与)
t.start(); // 打印 Thread-0(真正创建了一个新的操作系统线程去执行)
start() 内部做的事情远不止调用 run():它会向 JVM 申请分配新的线程资源、完成必要的初始化,然后由 JVM 底层机制在合适的时机去调度执行 run() 里的代码——这整个过程才是"启动一个线程",而单纯调用 run() 只是把这段代码当成普通方法在当前线程里跑一遍,状态机完全不会涉及 NEW/RUNNABLE 之类的转换。

六、面试追问链

延伸阅读
java.lang.Thread.State源码枚举定义与官方 Javadoc- 用
jstack抓取一个真实 Java 进程的线程堆栈,观察 BLOCKED / WAITING / TIMED_WAITING 线程各自在等什么 - 下一篇预告:Java 内存模型 JMM
问答(不计入阅读时长)
Q1:为什么**jstack**抓到的线程堆栈里,很多线程明明看起来在"运行",状态却显示 RUNNABLE,而不是更精确的"正在跑"或"排队等 CPU"?
A:因为 Java 的 Thread.State 只是 JVM 层面的抽象状态机,RUNNABLE 这一个值本身就同时覆盖了操作系统层面"正在占用 CPU 执行"和"已经就绪、在等待操作系统调度器分配时间片"这两种情况。JVM 认为只要线程没有被锁挡住、也没有主动进入等待,就统一算作"可运行",不再进一步区分底层操作系统调度细节。所以看到 RUNNABLE,不能直接断定这个线程这一刻真的在跑。
Q2:一个线程调用**synchronized**方法时抢不到锁,会进入哪个状态?和它主动调用**wait()**进入的状态是一回事吗?
A:不是一回事。抢 synchronized 锁失败进入的是 BLOCKED,这是被动的——线程什么都没做,只是在排队等锁释放,锁一释放它自动变回 RUNNABLE,不需要任何人显式唤醒。而调用 wait() 进入的是 WAITING,这是主动的——线程自己选择放弃锁并进入等待,必须等别的线程调用 notify()/notifyAll() 才会被唤醒。两者的判断标准是"是被锁卡住,还是自己选择等待"。
Q3:线程 A 调用**obj.wait()**之后被另一个线程**notify()**唤醒,它会立刻变回 RUNNABLE 开始执行吗?
A:不会立刻执行,但状态确实会先离开 WAITING。wait() 在唤醒后必须重新竞争之前释放掉的那把锁,这个"重新抢锁"的过程状态是 BLOCKED,只有真正抢到锁之后才会变回 RUNNABLE 继续往下执行。所以完整链路通常是 RUNNABLE → WAITING → BLOCKED → RUNNABLE,BLOCKED 和 WAITING 常常是接续发生的两段,不是互斥的单选项。
Q4:**Thread.sleep(1000)**和**obj.wait(1000)**都会让线程进入 TIMED_WAITING,它们对锁的处理完全一样吗?
A:完全不一样。sleep() 不会释放线程持有的任何锁,只是让当前线程原地暂停,其他线程仍然抢不到它手里的锁;wait() 则会释放调用时所持有的那个对象的锁,让其他线程有机会进入同步块。这也解释了为什么 wait() 必须写在 synchronized 块内部——它要释放的锁必须是自己已经持有的锁,否则直接抛 IllegalMonitorStateException。
Q5:对一个已经调用过**start()**的线程对象,再调用一次**start()**会发生什么?
A:会直接抛出 IllegalThreadStateException。NEW 是一张"仅一次可用"的门票,线程一旦启动过(哪怕已经跑到 TERMINATED),状态机就不允许它跳回 NEW 再重新 start 一次。如果需要"重新执行一遍同样的逻辑",正确做法是创建一个新的 Thread 对象,而不是复用旧对象再次 start()。
Q6:调用**thread.interrupt()**之后,目标线程是不是就停止运行了?
A:不是。interrupt() 只是给目标线程设置一个"中断标志位",本身不会强制终止任何东西。如果目标线程当时正阻塞在 sleep/wait/join 上,会立刻收到一个 InterruptedException 并被唤醒退出阻塞;但如果线程正在执行普通计算代码(没有阻塞在这些方法上),interrupt() 不会产生任何自动效果,线程会一直跑下去,除非代码里自己定期检查 Thread.interrupted() 或 isInterrupted() 并主动响应退出。
Q7:直接调用**thread.run()**而不是**thread.start()**,程序还能利用多核并发执行吗?
A:不能。run() 只是一次普通的方法调用,会在调用者当前所在的线程里同步执行,完全不会创建新的操作系统线程,Thread.currentThread().getName() 打印出来的还是原来那个线程(比如 main),整个过程也不涉及 NEW → RUNNABLE 之类的状态转换。只有调用 start(),JVM 才会真正申请一个新线程去执行 run() 里的代码,打印出来的会是新线程的名字(比如 Thread-0)。
Q8:一个线程执行完**run()**方法后进入 TERMINATED,之后还能不能通过某种方式让它"复活"继续执行?
A:不能。TERMINATED 是一个真正的终态,线程一旦到达这个状态就彻底结束了生命周期,无法再次 start()(会抛 IllegalThreadStateException),也没有任何 API 能让同一个 Thread 对象"复活"重新进入 RUNNABLE。如果业务上需要反复执行类似的任务,正确的做法是使用线程池复用工作线程,或者每次都创建新的 Thread 对象,而不是试图让一个已终止的线程对象再跑一次。