一、常规回答(八股文)
线程的生命周期一共分为有6个状态,在某个时刻中,一个线程只会处在6个状态中的其中一种。
第1:初始状态(NEW) 当前的线程已经被创建了出来,但是还没有通过调用start()方法来启用。
第2:运行状态(RUNNABLE) 当前的进程已经调用了start()方法,进入了运行的状态,或者正在等待CPU的调度。
第3:阻塞状态(BLOCKED) 当前的线程会暂停执行,等待某些事件的发生,比如等待锁的释放。
第4:等待状态(WAITING) 当前线程会暂停执行,等待其他线程的特定事件。
如果线程调用wait()方法导致暂停,则需其他线程调用notify方法唤醒当前线程;如果线程调用了其他线程的join()方法,则需等待join对应的线程执行完毕后,才能唤醒当前线程。
第5:计时等待状态(TIMED_WAITING) 线程暂停执行,但是在经历了一段时间后会自动被唤醒。如调用Thread.sleep()方法,一段时间内线程被阻塞。
第6:终止状态(TERMINATED) 当前线程的逻辑已经执行完毕,或者遇到了异常而终止,就会进入终止状态。
二、加深理解
加深理解面试题答案的最好方法,无非就是自己实践一次。
所以,为了能够检测到 Java 程序中各个线程的运行状态,这里使用了一个小工具:arthas-boot
2.1 arthas-boot 安装(可跳过)
官方文档:https://alibaba.github.io/arthas
下载连接:https://arthas.aliyun.com/arthas-boot.jar
arthas
实际上是 Alibaba 开源的 Java 诊断工具,它的特点是使用方便,功能强大。
最近学习了 JVM 调优相关的内容,使用到了这个工具,所以这里才想到使用它来检测 Java 程序的线程状态信息(如下图),来更好的理解线程在什么时候会进入什么状态。
这里简单说一下使用步骤,如不想安装则可以跳过直接看:[点击跳转] 2.2 开始检验线程的状态
第1步, 下载 arthas-boot.jar 文件:https://arthas.aliyun.com/arthas-boot.jar
第2步, 在本地系统运行一个Java程序,比如在 IDEA 中写一个死循环并运行:
第3步, 打开cmd
,进入文件所在目录,执行命令
java -jar arthas-boot.jar
随后选择需要挂载的 Java 程序,这里我们要监控的是 Test3 这个类,所以在控制台输入1
并回车。
等到出现以下 arthas
的图案,就说明运行成功了。
输入 thread 命令,即可查看当前进程的所有运行的线程:
2.2 检测线程的状态
2.2.1 初始状态(NEW)
当一个线程被创建但还没有调用start()
方法运行的时候,这时的线程状态应该是 NEW
。
这个状态下的线程用arthas
工具获取不到,所以就只能用代码获取了。
测试程序:
public class Test3 {public static void main(String[] args) throws InterruptedException {Thread test1 = new Thread(() -> {while (true) {// 模拟线程在执行某些操作}});test1.setName("test1");System.out.printf("线程[%s]当前的状态是: %s\n", test1.getName(), test1.getState());}
}
运行结果:
2.2.2 运行状态(RUNNABLE)
当一个线程被创建了,且调用了start()
方法后,该线程状态应为RUNNABLE
测试程序:
public class Test3 {public static void main(String[] args) throws InterruptedException {Thread test1 = new Thread(() -> {while (true) {// 模拟线程在执行某些操作}});test1.setName("test1");test1.start();doSomething();}/** 保持程序一直执行 */private static void doSomething() {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
运行结果:
2.2.3 阻塞状态(BLOCKED)
这个状态下的线程会暂停当前的执行。通常情况下,一个线程在等待锁(锁已被其他线程占用)的时候,这个线程会进入阻塞状态。
测试程序:
public class Test3 {public static void main(String[] args) throws InterruptedException {Thread test1 = new Thread(() -> {enter();});Thread test2 = new Thread(() -> {enter();});/*两个线程都尝试进入enter()方法,但只有一个线程能够拿到 `synchronized` 锁并进入方法,另外一个线程尝试进入方法但会被阻塞。*/test1.setName("test1");test2.setName("test2");test1.start();test2.start();}private synchronized static void enter() {System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());for (long j = 0L; j < 50000000000L; j++) {}System.out.printf("线程[%s]退出了,释放了锁\n", Thread.currentThread().getName());}
}
运行结果:
# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作(以上是目前为止已打印的结果)
此时,线程test1
处于运行状态,而线程test2
因为没有拿到锁(锁被test1
占用),所以处在阻塞BLOCKED
状态。
2.2.4 等待状态(WATING)
(1/2) 情况1:线程本身手动调用 wait() 方法
执行 wait()
方法的那个线程,在执行了该方法后,会释放锁,并进入等待状态WAITING
(只有别的线程调用notifyAll()
方法才能唤醒该线程)
测试程序:
public class Test3 {public static void main(String[] args) throws InterruptedException {Thread test1 = new Thread(() -> {enter();});Thread test2 = new Thread(() -> {enter();});test1.setName("test1");test2.setName("test2");test1.start();test2.start();}private synchronized static void enter() {System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());for (long j = 0L; j < 100000000000L; j++) {if (j == 30000000000L) {System.out.printf("线程[%s]执行到一半时,调用了wait()方法\n", Thread.currentThread().getName());try {// 冷知识1:wait()方法必须通过同步监视器对象(monitor)来调用// 冷知识2:静态的同步方法中,monitor默认是当前类的class对象,即Test3.class// 所以这里必须通过"Test3.class"来调用wait()方法,否则会报错Test3.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}}System.out.printf("线程[%s]执行完毕\n", Thread.currentThread().getName());}
}
运行结果:
# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作
线程[test1]执行到一半时,调用了wait()方法
线程[test2]进来了,它将执行以下耗时的业务操作 # 此处说明了wait()方法会释放锁(以上是目前为止已打印的结果)
此时,第一个线程(test1
)在方法中执行了wait()
后,进入了WAITING
状态。
(2/2) 情况2:某线程调用其他线程的join()方法
测试程序:
public class Test3 {private static Thread test1;private static Thread test2;public static void main(String[] args) throws InterruptedException {test1 = new Thread(() -> {System.out.println("线程test1开始执行");for (long j = 0L; j < 100000000000L; j++) {if (j == 30000000000L) {System.out.println("线程test1执行到一半时,调用了t2的join()方法");try {test2.join();} catch (InterruptedException e) {e.printStackTrace();}}}System.out.println("线程test1执行完毕");});test2 = new Thread(() -> {System.out.println("线程test2开始执行");for (long j = 0L; j < 100000000000L; j++) {}System.out.println("线程test2执行完毕");});test1.setName("test1");test2.setName("test2");test1.start();test2.start();}
}
运行结果:
# 控制台打印信息:
线程test1开始执行
线程test2开始执行
线程test1执行到一半时,调用了test2的join()方法 (以上是目前为止已打印的结果)
由于线程test1
调用了test2.join()
,因此线程test1
会暂停执行,只有test2
执行完毕后才被唤醒。
此时test1
处在等待状态WAITING
2.2.5 计时等待状态(TIMED_WAITING)
(1/3) 情况1:线程本身手动调用 wait(time) 方法
和 2.2.4 等待状态(WAITING)情况1 其实是类似的,只是调用的方法由wait()
变成了wait(time)
测试程序:
public class Test3 {public static void main(String[] args) throws InterruptedException {Thread test1 = new Thread(() -> {enter();});Thread test2 = new Thread(() -> {enter();});test1.setName("test1");test2.setName("test2");test1.start();test2.start();}private synchronized static void enter() {System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());for (long j = 0L; j < 100000000000L; j++) {if (j == 30000000000L) {System.out.printf("线程[%s]执行到一半时,调用了wait(time)方法\n", Thread.currentThread().getName());try {// wait()方法必须通过同步监视器(monitor)来调用// 冷知识:静态的同步方法中,monitor默认是当前类的class对象,即Test3.class// 所以这里必须通过"Test3.class"来调用wait()方法,否则会报错Test3.class.wait(10 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}}System.out.printf("线程[%s]执行完毕\n", Thread.currentThread().getName());}
}
运行结果:
# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作
线程[test1]执行到一半时,调用了wait(time)方法
线程[test2]进来了,它将执行以下耗时的业务操作(以上是目前为止已打印的结果)
此时test1
线程进入了计时等待状态(TIMED_WAITING
)
(2/3) 情况2:某线程调用其他线程的join(time)方法
和 2.2.4 等待状态(WAITING)情况2 其实是类似的,只是调用的方法由join()
变成了join(time)
测试程序:
public class Test3 {private static Thread test1;private static Thread test2;public static void main(String[] args) throws InterruptedException {test1 = new Thread(() -> {System.out.println("线程test1开始执行");for (long j = 0L; j < 100000000000L; j++) {if (j == 30000000000L) {System.out.println("线程test1执行到一半时,调用了test2的join(time)方法");try {test2.join(10 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}}System.out.println("线程test1执行完毕");});test2 = new Thread(() -> {System.out.println("线程test2开始执行");for (long j = 0L; j < 100000000000L; j++) {}System.out.println("线程test2执行完毕");});test1.setName("test1");test2.setName("test2");test1.start();test2.start();}
}
运行结果:
# 控制台打印信息:
线程test1开始执行
线程test2开始执行
线程test1执行到一半时,调用了test2的join(time)方法 (以上是目前为止已打印的结果)
由于线程test1
调用了test2.join(time)
,test1
会处在计时等待状态TIMED_WAITING
(3/3) 情况3:某线程调用sleep(time)方法
线程调用Thread.sleep(time)
方法后,也会进入计时等待状态(TIMED_WAITING
)
测试程序:
public class Test3 {public static void main(String[] args) throws InterruptedException {Thread test1 = new Thread(() -> {try {System.out.println("年轻人身体就是好,一进来工作时倒头就睡");Thread.sleep(100 * 1000);} catch (InterruptedException e) {e.printStackTrace();}});test1.setName("test1");test1.start();}
}
运行结果:
# 控制台打印信息:
年轻人身体就是好,一进来工作时倒头就睡
2.2.6 终止状态(TERMINATED)
当一个线程逻辑已执行完毕,或出现了异常而终止的时候,此时线程状态应该是 TERMINATED
。
这个状态下的线程用arthas工具获取不到,所以就只能用代码获取了。
测试程序:
public class Test3 {public static void main(String[] args) throws InterruptedException {Thread test1 = new Thread(() -> {System.out.println("test1执行过程出现异常");int a = 1 / 0;});Thread test2 = new Thread(() -> {for (int i = 0; i < 100; i++) {int a = 666;}System.out.println("test2任务已执行完毕");});test1.setName("test1");test2.setName("test2");test1.start();test2.start();Thread.sleep(1000);System.out.printf("线程[%s]的状态: %s\n", test1.getName(), test1.getState());System.out.printf("线程[%s]的状态: %s\n", test2.getName(), test2.getState());}
}
运行结果:
# 控制台打印信息:
test1执行过程出现异常
test2任务已执行完毕
Exception in thread "test1" java.lang.ArithmeticException: / by zeroat draft.Test3.lambda$main$0(Test3.java:8)at java.lang.Thread.run(Thread.java:748)
线程[test1]的状态: TERMINATED
线程[test2]的状态: TERMINATED