起源:https://blog.csdn.net/limenghua9112/article/details/106975105

为何要理解Java线程状态

线程是 JVM 执行工作的最小单元,了解线程的状态转换是了解后续多线程问题的根底。

Java线程状态转换图

Java线程有哪些状态?

在 JVM 运行中,线程一共有 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 六种状态,这些状态对应 Thread.State 枚举类中的状态。

举荐一个开源收费的 Spring Boot 实战我的项目:

https://github.com/javastacks/spring-boot-best-practice

Thread.State枚举源码:

为不便浏览,在此去掉了文档正文

public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;}

在给定的工夫点,线程只能处于这些状态中的一种状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

NEW,TERMINATED

这两个状态比拟好了解,当创立一个线程后,还没有调用start()办法时,线程处在 NEW 状态,线程实现执行,退出后变为TERMINATED终止状态。

RUNNABLE

运行 Thread 的 start 办法后,线程进入 RUNNABLE 可运行状态

/** * 程序目标:察看线程的各种状态 * created at 2020-06-26 19:09 * @author lerry */class MyThread extends Thread { @Override public void run() {  System.out.printf("%s线程运行\n", Thread.currentThread().getName()); }}/** * 别离察看创立线程后、start()后、和线程退出后的线程状态。 * 其中Thread.sleep(50);是为了期待线程执行完 */public class ThreadStateDemo { public static void main(String[] args) throws InterruptedException {  MyThread myThread = new MyThread();  System.out.printf("创立线程后,线程的状态为:%s\n", myThread.getState());  myThread.start();  System.out.printf("调用start()办法后线程的状态为:%s\n", myThread.getState());  // 休眠50毫秒,期待MyThread线程执行完  Thread.sleep(50);  System.out.printf("再次打印线程的状态为:%s\n", myThread.getState()); }}

输入后果:

创立线程后,线程的状态为:NEW调用start()办法后线程的状态为:RUNNABLEThread-0线程运行再次打印线程的状态为:TERMINATED

咱们能够看到,输入后果合乎预期。

  • 在刚创立完线程后,状态为NEW
  • 调用了start()办法后线程的状态变为:RUNNABLE。
  • 而后,咱们看到了run()办法的执行,这个执行,是在主线程main打印了调用start()办法后线程的状态为:RUNNABLE输入后执行的。
  • 随后,咱们让main线程休眠了50毫秒,期待MyThread线程退出
  • 最初再打印MyThread线程的状态,为TERMINATED。

BLOCKED

如图左侧所示,在运行态中的线程进入 synchronized 同步块或者同步办法时,如果获取锁失败,则会进入到 BLOCKED 状态。当获取到锁后,会从 BLOCKED 状态复原到就绪状态。

import lombok.extern.slf4j.Slf4j;/** * 程序目标:察看线程的BLOCKED状态 * created at 2020-06-26 19:09 * @author lerry */@Slf4jpublic class ThreadBlockedStateDemo { public static void main(String[] args) {  Thread threadA = new Thread(() -> method01(), "A-Thread");  Thread threadB = new Thread(() -> method01(), "B-Thread");  threadA.start();  threadB.start();  log.info("线程A的状态为:{}", threadA.getState());  log.info("线程B的状态为:{}", threadB.getState()); } /**  * 进展10毫秒、模仿办法执行耗时  */ public static synchronized void method01() {  log.info("[{}]:开始执行主线程的办法", Thread.currentThread().getName());  try {   Thread.sleep(10);  }  catch (InterruptedException e) {   e.printStackTrace();  }  log.info("[{}]:主线程的办法执行结束", Thread.currentThread().getName()); }}

输入后果:

2020-06-26 20:32:15.404 [A-Thread] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - [A-Thread]:开始执行主线程的办法2020-06-26 20:32:15.404 [main    ] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - 线程A的状态为:RUNNABLE2020-06-26 20:32:15.407 [main    ] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - 线程B的状态为:BLOCKED2020-06-26 20:32:15.417 [A-Thread] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - [A-Thread]:主线程的办法执行结束2020-06-26 20:32:15.418 [B-Thread] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - [B-Thread]:开始执行主线程的办法2020-06-26 20:32:15.430 [B-Thread] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - [B-Thread]:主线程的办法执行结束

A线程优先取得到了锁,状态为RUNNABLE,这时,B线程处于BLOCKED状态。

当A线程执行结束后,B线程执行对应办法。

举荐一个开源收费的 Spring Boot 实战我的项目:

https://github.com/javastacks/spring-boot-best-practice

WAITING,TIMED_WAITING

如图右侧所示,运行中的线程还会进入期待状态,这两个期待一个是有超时工夫的期待,例如调用 Object.waitThread.join 等;另外一个是无超时的期待,例如调用 Thread.join 或者 Locksupport.park等。这两种期待都能够通过 notify 或 unpark 完结期待状态并复原到就绪状态。

官网文档阐明为:

A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.

处于期待状态的线程正在期待另一个线程执行特定的操作。

import lombok.extern.slf4j.Slf4j;/** * <pre> * 程序目标:察看线程的WAITING状态 * 模仿:只有一个售票窗口的售票厅,有两个粉丝都想买票。 * 如果没有票,他们就持续期待、如果有票,则买票、而后来到售票厅。 * 其中,工作人员会补票,补票之后,粉丝就能够买到票了。 * </pre> * created at 2020-06-26 19:09 * @author lerry */@Slf4jpublic class ThreadWaitingStateDemo { public static void main(String[] args) throws InterruptedException {  Ticket ticket = new Ticket();  Thread threadA = new Thread(() -> {   synchronized (ticket) {    while (ticket.getNum() == 0) {     try {      ticket.wait();     }     catch (InterruptedException e) {      e.printStackTrace();     }    }    ticket.buy();   }  }, "粉丝A");  Thread threadB = new Thread(() -> {   synchronized (ticket) {    while (ticket.getNum() == 0) {     try {      ticket.wait();     }     catch (InterruptedException e) {      e.printStackTrace();     }    }    ticket.buy();   }  }, "粉丝B");  threadA.start();  threadB.start();  // 确保A和B线程都运行起来  Thread.sleep(10);  log.info("粉丝A线程的状态为:{}", threadA.getState());  log.info("粉丝B线程的状态为:{}", threadB.getState());  Thread employeeThread = new Thread(() -> {   synchronized (ticket) {    if (ticket.getNum() == 0) {     ticket.addTickt();     ticket.notifyAll();    }   }  }, "补票员");  employeeThread.start(); }}@Slf4jclass Ticket { /**  * 票的张数  */ private int num = 0; public int getNum() {  return num; } public void addTickt() {  try {   Thread.sleep(2_000);  }  catch (InterruptedException e) {   e.printStackTrace();  }  log.info("补充票");  this.num += 2; } /**  * 进展10毫秒、模仿办法执行耗时  */ public void buy() {  log.info("[{}]:购买了一张票", Thread.currentThread().getName());  log.info("[{}]:退出售票厅", Thread.currentThread().getName()); }}

输入:

2020-06-26 21:26:37.938 [main    ] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝A线程的状态为:WAITING2020-06-26 21:26:37.945 [main    ] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝B线程的状态为:WAITING2020-06-26 21:26:39.948 [补票员     ] INFO  com.hua.threadtest.state.Ticket - 补充票2020-06-26 21:26:39.949 [粉丝B     ] INFO  com.hua.threadtest.state.Ticket - [粉丝B]:购买了一张票2020-06-26 21:26:39.949 [粉丝B     ] INFO  com.hua.threadtest.state.Ticket - [粉丝B]:退出售票厅2020-06-26 21:26:39.949 [粉丝A     ] INFO  com.hua.threadtest.state.Ticket - [粉丝A]:购买了一张票2020-06-26 21:26:39.949 [粉丝A     ] INFO  com.hua.threadtest.state.Ticket - [粉丝A]:退出售票厅

当批改ticket.wait();ticket.wait(10);后,输入后果如下:

2020-06-26 21:27:10.704 [main    ] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝A线程的状态为:TIMED_WAITING2020-06-26 21:27:10.709 [main    ] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝B线程的状态为:TIMED_WAITING2020-06-26 21:27:12.714 [补票员     ] INFO  com.hua.threadtest.state.Ticket - 补充票2020-06-26 21:27:12.714 [粉丝B     ] INFO  com.hua.threadtest.state.Ticket - [粉丝B]:购买了一张票2020-06-26 21:27:12.714 [粉丝B     ] INFO  com.hua.threadtest.state.Ticket - [粉丝B]:退出售票厅2020-06-26 21:27:12.715 [粉丝A     ] INFO  com.hua.threadtest.state.Ticket - [粉丝A]:购买了一张票2020-06-26 21:27:12.715 [粉丝A     ] INFO  com.hua.threadtest.state.Ticket - [粉丝A]:退出售票厅

对于wait()放在while循环的疑难

为什么ticket.wait();要放在while (ticket.getNum() == 0)代码块中呢?既然这行代码时让线程期待着,那应用if不就行了?

咱们构想一下,如果应用if,则在线程被唤醒后,会持续往下执行,不再判断条件是否合乎,这时还是没有票,粉丝也就购买不到票了。

咱们看一下Object.wait()的官网doc阐明:

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:           synchronized (obj) {               while (<condition does not hold>)                   obj.wait();               ... // Perform action appropriate to condition           }

在一个参数版本中(wait办法),中断和虚伪的唤醒是可能的,这个办法应该总是在循环中应用。

咱们再持续看Object.wait(long timeout)的文档阐明:

A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops

线程也能够在没有告诉、中断或超时的状况下被唤醒,这就是所谓的假唤醒。尽管这种状况在实践中很少产生,但应用程序必须通过测试导致线程被唤醒的条件来避免这种状况产生,如果条件不满足,则持续期待。换句话说,期待应该总是在循环中产生

所以,为了防止很少产生的假唤醒呈现时程序产生不可预知的谬误,倡议把wait()调用放在循环语句中。这样就算被假唤醒,也有条件语句的限度。

这也是为何wait要放在循环语句中的一个起因。

BLOCKED 和 WAITING 状态的区别和分割

表:处于期待状态的各种细分状态比照

简略来说,处于BLOCKED状态的线程,还是在竞争锁的,一旦cpu有工夫,它竞争到了锁、就会执行。

然而WAITING状态的线程则不去竞争锁,须要期待被动告诉、或者本人定的闹钟(等待时间)到了、再去竞争锁。

一图胜千言,在此援用一张国外一位大牛画的图:

近期热文举荐:

1.1,000+ 道 Java面试题及答案整顿(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!