关于java:面试官Java-线程有哪几种状态它们之间是怎么切换的

30次阅读

共计 7252 个字符,预计需要花费 19 分钟才能阅读完成。

起源: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()办法后线程的状态为:RUNNABLE
Thread- 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
 */
@Slf4j
public 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 的状态为:RUNNABLE
2020-06-26 20:32:15.407 [main] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - 线程 B 的状态为:BLOCKED
2020-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
 */
@Slf4j
public 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();}

}

@Slf4j
class 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 线程的状态为:WAITING
2020-06-26 21:26:37.945 [main] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝 B 线程的状态为:WAITING
2020-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_WAITING
2020-06-26 21:27:10.709 [main] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝 B 线程的状态为:TIMED_WAITING
2020-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 开发手册(嵩山版)》最新公布,速速下载!

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

正文完
 0