【Java线程的状态】 Java 线程的状态概述简要介绍 Java 并发编程中的线程状态理解线程状态对调试和程序性能优化有很大的帮助。Java线程的状态前面我们介绍过Thread类的基本用法其中有一个APIgetState。这个方法可以用来获取Java的线程状态但是当时并没有详细的介绍Java的线程状态我们来看一下Thread类的源码publicstaticenumState{NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;privateState(){}}可以看到Java的线程状态分为六种NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATEDNEW线程创建了但是没有运行起来。RUNNABLE可工作的又分为就绪状态和正在 CPU 上运行。BLOCKED由于锁竞争引起的阻塞join 引起的阻塞没有期限死等。TIMED_WAITING这几个都表示排队等着其他事情sleep 引起的阻塞join超时引起阻塞有期限。TERMINATED线程还在但是 start 方法已经执行完了。我们通过代码和 jconsole 工具来查看 Java 线程状态的切换packagethread;publicclassDemoState1{publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadtnewThread(()-{for(inti0;i1000000000;i){// 故意让线程运行一会}});// 1. 创建线程但没有 startSystem.out.println(start之前t.getState());t.start();// 2. start之后System.out.println(start之后t.getState());// 等待t结束t.join();// 3. run执行完成System.out.println(线程结束t.getState());}}代码分析我们利用 lambda 表达式重写了 Thread 类中的 run 方法run 方法中只是让线程运行一段时间。然后在 main 线程中通过 start 方法启动我们自定义的线程启动之后通过 getState 方法获取自定义线程的状态。然后通过 join 方法告诉当前的线程也就是 main 线程等待调用 join 方法的线程也就是 t 线程结束。t 线程结束之后再打印当前线程的状态。预计执行结果start之前NEWstart之后RUNNABLE线程结束TERMINATED实际执行结果结果分析在我们创建了 t1 线程之后、线程启动之前t1 线程的状态为NEW执行了start方法之后线程的状态切换为RUNNABLE。注意Java 线程的RUNNABLE包含了线程准备就绪到 CPU 上执行和线程已经在 CPU 上执行。线程的run方法执行结束之后t1 线程的状态切换为TERMINATED。packagethread;publicclassDemoState2{publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadtnewThread(()-{System.out.println(t开始运行);try{Thread.sleep(3000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(t运行结束);});t.start();// 防止main检查太快此时t还没进入sleepThread.sleep(500);System.out.println(t当前状态t.getState());t.join();System.out.println(t最终状态t.getState());}}代码分析我们还是定义了一个自定义的线程 t在run方法中写了一个sleep(3000)即让线程休眠 3s并捕获InterruptedException异常。在 main 线程中启动这个线程并获取这个线程的状态然后执行join方法让 main 线程等待 t 线程执行结束最后再获取一次 t 线程的状态。预计执行结果第一次打印TIMED_WAITING第二次打印TERMINATED。实际执行结果可以看到程序的运行结果是符合我们的预期的我们来分析一下线程刚刚创建的时候处于NEW状态然后执行start方法进入线程的run方法此时线程处于RUNNABLE状态执行sleep(3000)之后进入TIMED_WAITING状态。注意TIMED_WAITING这个状态和WAITING状态的区别前者是有限死等后者几乎是无限死等。在join方法执行之后main 线程等待 t 线程执行结束之后 t 线程的状态为TERMINATED。packagethread;publicclassDemoState3{publicstaticvoidmain(String[]args)throwsInterruptedException{finalObjectlockernewObject();Threadt1newThread(()-{synchronized(locker){System.out.println(t1拿到锁);try{Thread.sleep(5000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(t1释放锁);}},t1);Threadt2newThread(()-{System.out.println(t2准备获取锁);synchronized(locker){System.out.println(t2拿到锁);}},t2);t1.start();// 保证t1先把locker锁拿走Thread.sleep(500);t2.start();// 给t2一点时间让它执行到 synchronizedThread.sleep(500);System.out.println(t1状态t1.getState());System.out.println(t2状态t2.getState());t1.join();t2.join();}}接下来我们看一下线程状态BLOCKED来看代码我们定义了一个用final修饰的锁对象然后创建了两个线程在线程的run方法中利用synchronized关键字让两个线程竞争这个锁对象。在一个线程持有锁的时候另一个线程无法访问锁对象会触发线程状态BLOCKED。预期结果我们在 main 线程中用了两次Thread.sleep()方法确保 main 线程等待两个线程的synchronized代码块执行。我们首先启动了 t1 线程t1 线程会首先访问临界区资源而在 t1 线程执行sleep方法的时候 t1 线程的状态为TIMED_WAITING而 t2 正在等候临界区资源释放所以此时的状态为BLOCKED阻塞。执行结果packagethread;publicclassDemoState4{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt1newThread(()-{try{Thread.sleep(5000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}},t1);Threadt2newThread(()-{System.out.println(t2准备等待t1);try{t1.join();}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(t1结束t2继续运行);},t2);t1.start();t2.start();Thread.sleep(500);System.out.println(t1t1.getState());System.out.println(t2t2.getState());t1.join();t2.join();}}DemoState4 分析DemoState4演示了无超时join产生的WAITING状态。t1线程启动后进入sleep(5000)在这 5 秒内处于TIMED_WAITING。t2线程调用了t1.join()不带超时参数因此t2会一直等待t1结束处于WAITING状态。主线程在启动两个线程后sleep(500)再打印状态此时t1仍在 sleepTIMED_WAITINGt2则在join上等待WAITING。运行结果中t1为TIMED_WAITINGt2为WAITING验证了join无超时会导致等待线程进入WAITING。packagethread;publicclassDemoState5{publicstaticvoidmain(String[]args)throwsInterruptedException{ObjectlockernewObject();ThreadtnewThread(()-{synchronized(locker){System.out.println(t准备wait);try{locker.wait();}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(t被唤醒);}});t.start();Thread.sleep(500);System.out.println(wait之后t.getState());synchronized(locker){locker.notify();}t.join();System.out.println(最终状态t.getState());}}DemoState5 分析DemoState5演示了Object.wait()产生的WAITING状态。线程t在synchronized块中调用locker.wait()释放锁并进入WAITING状态。主线程在sleep(500)后打印t.getState()此时t正在wait所以输出WAITING。主线程随后获取locker锁并调用locker.notify()唤醒t。t被唤醒后需要重新竞争锁成功拿到锁后继续执行最终变为TERMINATED。这个例子清晰展示了wait导致线程进入WAITING以及notify唤醒后线程回到RUNNABLE并重新争锁的完整流程。线程状态总结表下表汇总了 Java 六种线程状态的定义、触发条件及常见场景状态定义触发条件常见场景NEW线程已创建但尚未调用start()方法new Thread()后、start()前线程对象刚创建等待启动RUNNABLE线程处于可运行状态包括正在 JVM 中运行和等待 CPU 调度调用start()从阻塞/等待状态被唤醒后重新获得执行资格线程正常执行notify唤醒后重新争锁BLOCKED线程被阻塞正在等待获取监视器锁进入synchronized块/方法时锁被其他线程持有多线程竞争同一把锁synchronized临界区等待WAITING线程无限期等待另一个线程执行特定操作Object.wait()无超时Thread.join()无超时LockSupport.park()生产者-消费者模型中的wait/notify等待子线程结束TIMED_WAITING线程在指定时间内等待另一个线程执行特定操作Thread.sleep(ms)Object.wait(ms)Thread.join(ms)LockSupport.parkNanos()/parkUntil()定时任务延迟超时等待获取锁带超时的join等待TERMINATED线程已终止run()方法执行完毕run()方法正常结束或抛出未捕获的异常线程任务执行完成等待 GC 回收线程状态转换图start获取锁失败获取锁成功wait / join无超时notify / notifyAll 或 join 的线程结束sleep / join超时 / wait超时超时或 notifyrun 结束NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED常见面试题解析1. Java 中RUNNABLE状态和操作系统线程的RUNNING状态有什么区别关键点Java 线程模型中没有RUNNING状态RUNNABLE同时涵盖了“就绪Ready”和“正在运行Running”两种操作系统层面的状态。详细解释在 JVM 中线程调用start()后进入RUNNABLE状态。但这个状态并不区分线程是否正在 CPU 上执行还是等待 CPU 调度。当线程获得 CPU 时间片时它实际在运行当时间片用完或被抢占它回到就绪队列等待调度但在 JVM 看来它仍然是RUNNABLE。因此面试中常问的“RUNNABLE和RUNNING的区别”本质上是考察对 Java 线程状态模型的抽象理解Java 将操作系统的 Ready 和 Running 统一抽象为RUNNABLE简化了开发模型。示例说明在DemoState1中线程t在start()之后、run()执行期间输出RUNNABLE无论它正占用 CPU 执行空循环还是被调出等待下一次调度。2.BLOCKED和WAITING状态的根本区别是什么关键点BLOCKED线程等待获取监视器锁synchronized锁是被动等待锁资源释放。WAITING线程主动等待另一个线程执行特定动作如notify、join完成必须由其他线程显式唤醒。详细解释进入BLOCKED的唯一方式当一个线程尝试进入synchronized方法/块但锁已被其他线程持有时进入BLOCKED状态。一旦持有锁的线程释放锁JVM 自动唤醒一个BLOCKED线程去竞争锁无需显式通知。进入WAITING的常见方式Object.wait()、Thread.join()无超时、LockSupport.park()。这些方法会释放锁如果持有并进入无限等待直到其他线程调用notify()/notifyAll()或join的目标线程结束。关键区别WAITING可以被notify唤醒BLOCKED只能等待锁释放后自动参与竞争WAITING释放锁BLOCKED不持有锁只是等待获取锁。示例说明DemoState3中t2等待synchronized (locker)时处于BLOCKED。DemoState4中t2调用t1.join()无超时处于WAITING。DemoState5中线程t调用locker.wait()后释放锁进入WAITING直到主线程notify才被唤醒。3.sleep()和wait()的区别是什么关键点对比维度Thread.sleep()Object.wait()所属类Thread类的静态方法Object类的实例方法是否释放锁不释放锁释放锁调用位置任何地方必须在synchronized块/方法中调用唤醒方式超时自动唤醒或interrupt()需要notify()/notifyAll()或超时带参数进入状态TIMED_WAITINGWAITING无超时或TIMED_WAITING带超时示例说明sleep不释放锁如果线程在synchronized块中调用Thread.sleep(1000)它仍然持有锁其他线程无法进入该同步块。wait释放锁线程在synchronized块中调用locker.wait()后会释放locker锁让其他线程有机会进入同步块。// 演示 wait 释放锁sleep 不释放锁ObjectlocknewObject();// 线程 A持有锁后 sleepnewThread(()-{synchronized(lock){System.out.println(A 持有锁sleep...);try{Thread.sleep(2000);}catch(InterruptedExceptione){}System.out.println(A 释放锁);}}).start();// 确保 A 先拿到锁Thread.sleep(100);// 线程 B尝试获取锁newThread(()-{synchronized(lock){System.out.println(B 拿到了锁);}}).start();在上面的代码中线程 B 必须等待 A 的sleep结束并退出synchronized块才能拿到锁说明sleep不释放锁。如果换成lock.wait()则 B 会立即拿到锁因为wait释放锁。