1、线程状态
Java 中线程状态有五种,在任意一个时间点,一个线程只能有且只有其中一种状态。其中五种状态分别是:NEW、RUNNABLE、WAITING、TIMED_WAITING、BLOCKED、TERMINATED。线程状态图如下:
1.1、初始状态 NEW
当我们用关键字 new 创建了一个实现 Runnable 和继承 Thread 的线程的对象,但还没有调用 start(),线程的状态为 NEW 状态,准确地说,它只是 Thread 对象的状态,因为没有 start 之前,该线程根本不存在,与你用关键字 new 创建一个普通的 Java 对象没有什么区别。
1.2、运行 RUNNABLE
线程进入 RUNNABLE 状态,就必须调用 start 方法,那么此时才是真正地在 JVM 进程中创建一个线程。一个正在 RUNNING 状态的线程事实上也是 RUNNABLE 的,但是反过来则不成立。RUNNABLE 包括了操作系统线程状态中的 RUNNING 和 READY。
- 线程对象创建后,调用该对象的 start(),该状态的线程位于可运行线程池中
- 等待被线程调度选中,获取 CPU 的使用权,此时处于 ready 状态
- 就绪状态的线程在获得 CPU 时间片后变为 running 状态
进入 running 状态的线程,可以发生如下状态转换
- 直接进入 TERMINATED 状态,比如调用 JDK 已经不推荐使用的 stop 方法或者判断某个逻辑标识
- 进入 BLOCKED 状态,比如调用了 sleep、 wait 方法而加入了 waitSet 中
- 进行某个阻塞的 IO 操作,比如因网络数据的读写进入了 BLOCKED 状态
- 获取某个锁资源,从而加入到该锁的阻塞队列中而进入 BLOCKED 状态
- 由于 CPU 的调度器轮询使该线程放弃执行,进入 ready 状态
- 线程主动调用 yield 方法,放弃 CPU 执行权,进入 ready 状态
1.3、无限期等待 WAITING
处于这种状态的线程不会被分配 CPU 执行时间,需要等待其他线程显示地(通知或中断)唤醒,否则会处于无限期等待的状态。以下方法会让线程陷入无限期的等待状态:
- 没有设置 Timeout 参数的 Object.wait()
- 没有设置 Timeout 参数的 Object.join()
- LockSupport.park()
1.4、超时等待 TIMED_WAITING
处于这种状态的线程不会被分配 CPU 执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会由系统自动唤醒。以下方法会让线程进入超时等待状态:
- Thread.sleep()
- 设置 Timeout 参数的 Object.wait()
- 设置 Timeout 参数的 Object.join()
- LockSupport.parkNanos()
- LockSupport.parkUntil()
1.5、阻塞 BLOCKED
阻塞状态和等待状态的区别是:阻塞状态在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而等待状态则是在等待一段时间或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。线程在 BLOCKED 状态中可以切换至如下几个状态。
- 直接进入 TERMINATED 状态,比如调用 JDK 已经不推荐使用的 stop 方法或者意外死亡 JVM Crash
- 线程阻塞的操作结束,比如读取了想要的数据字节进入到 RUNNABLE 状态
- 线程完成了指定时间的休眠,进入到 RUNNABLE 状态
- Wait 中的线程被其他线程 notify/notifyAll 唤醒,进入 RUNNABLE 状态
- 线程获取到某个锁资源,进入到 RUNNABLE 状态
- 线程在阻塞过程中被打断,比如其他线程调用了 interrupt 方法,进入到 RUNNABLE 状态
1.6、终止 TERMINATED
TERMINATED 是一个线程的最终状态,在该状态中线程将不会切换到其他任何状态,线程进入 TERMINATED 状态,意味着线程的整个生命周期都结束了,线程一旦终止了,就不能复生。在一个终止的线程上调用 start() 方法,会抛出 java.lang.IllegalThreadStateException 异常。下列这些情况将会是线程进入 TERMINATED 状态。
- 线程运行正常结束,结束生命周期
- 线程运行出错意外结束
- JVM Crash,导致所有的线程都结束
2、线程状态代码演示
package com.yj.thread;
import sun.jvm.hotspot.opto.Block;
import java.util.concurrent.TimeUnit;
/**
* @description: 线程状态
* @author: erlang
* @since: 2021-02-01 19:57
*/
public class ThreadState {
public static void main(String[] args) {
new Thread(new TimeWaiting(), "TimeWaitingThread").start();
new Thread(new Waiting(), "WaitingThread").start();
// 使用两个 Blocked 线程,一个获取锁成功,另个被阻塞
new Thread(new Blocked(), "BlockedThread-1").start();
new Thread(new Blocked(), "BlockedThread-2").start();
}
/**
* 给线程设置一个很大的睡眠时间
*/
static class TimeWaiting implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 线程在 Waiting.class 实例上等待
*/
static class Waiting implements Runnable {
@Override
public void run() {
synchronized (Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Blocked implements Runnable {
@Override
public void run() {
synchronized (Block.class) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行 ThreadState 代码后,在终端使用 jps 和 jstack 查看堆栈信息,jps 和 jstack 用法如图所示:
堆栈日志如下:
3、Daemon 线程
Daemon 线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon 线程的时候,Java 虚拟机将会退出。可以通过调用 Thread.setDaemon(true) 将线程设置为 Daemon 线程。
package com.yj.thread;
import java.util.concurrent.TimeUnit;
/**
* @description: Daemon 线程
* @author: erlang
* @since: 2021-02-01 21:33
*/
public class DaemonThread {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunnable(), "DaemonRunnable");
thread.setDaemon(true);
thread.start();
}
static class DaemonRunnable implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("DaemonRunnable finally");
}
}
}
}
在 idea 中运行 DaemonThread 程序,可以看到控制台中并没有任何输出。main 线程(非 Daemon 线程)在启动了线程 DaemonRunnable 之后,随着 main 方法执行完毕而终止,而此时 Java 虚拟机中已经没有非 Daemon 线程了,虚拟机需要退出。Java 虚拟机中的所有 Daemon 线程都需立即终止,因此 DaemonRunnable 立即终止,但是 DaemonRunnable 中的 finally 块并没有执行。
注意: 在构建 Daemon 线程时,不能在 finally 块中执行关闭或清理资源逻辑