在编程中,很多程序都是有生命周期的,比如熟悉的spring bean。线程也不例外。线程其实是操作系统的概念,很多编程语言对操作系统的线程状态进行了封装了,有了自己的差异性,不过,基本上都是雷同的。
在并发编程中,线程的状态至关重要。刚接触线程的时候,线程的各种状态及其状态转换,会让人感觉很乱,一定要区分操作系统的线程状态和Java语言的线程状态。Java语言对于操作系统的线程做了包装,底层还是操作系统的线程。本节讲解操作系统通用线程状态和Java语言的线程状态。
1.操作系统的通用线程状态
操作系统的通用线程状态,总共有5个状态,初始状态、可运行状态、运行状态、休眠状态和终止状态。如下图所示:
1.初始状态
线程已创建,但是不允许分配CPU执行。这里是属于编程语言层面的,操作系统还没创建线程。
2.可运行状态
指线程可以分配CPU去执行。操作系统已经创建了线程。
3.运行状态
处在可运行状态的线程被操作系统分配CPU时间片后,线程就从可运行状态转为可运行状态。
4.休眠状态
运行状态的线程调用阻塞的API(比如阻塞IO)或者等待某个事件或条件,线程就会转换为休眠状态。同时释放CPU使用权,注意休眠状态的线程永远没有机会获得CPU的使用权,也就不会执行,只有当等待的事件或条件满足了,线程会从休眠状态转换到可运行状态。
5.终止状态
线程执行完毕或者出现异常就会进入终止状态。终止状态表明线程的生命周期结束,不会转为其他的状态了。
2.Java语言的线程状态
在Java中,总共有六种状态:
- NEW(初始化状态)R
- RUNNABLE(可运行 / 运行状态)
- BLOCKED(阻塞状态)
- WAITING(无时限等待)
- TIME_WAITING(有时限等待)
- TERMINATED(终止状态)
可以看下Java源码,在java.lang.Thread.State中就有定义。通过如下图做下对比:
首先,Java把操作系统可运行和运行状态统一合并到RUNNABLE状态。将休眠状态细分为三种,
BLOCKED/WAITING/TIMED_WAITING,这三种状态不会获取的CPU的使用权。
其实Java的线程状态转换,除了了线程的生死,我们最需要关注的是RUNNABLE和休眠状态之间的转换即可。这里总结了一张图:
2.1 NEW
如下代码:
public class ThreadState {
public static void main(String[] args) {
Thread t = new Thread(()-> System.out.println("hello world"));
System.out.println(t.getState());
}
}
运行后,在控制台上输出:NEW,通过Thread new出来一个线程时,该线程就是NEW状态。
2.2 从NEW到RUNNABLE
我们创建线程的两种方式,分别是继承Thread和实现Runnable接口,当我们调用start方法时,线程就从NEW状态转换为RUNNABLE状态。
2.3 RUNNABLE与BLOCKED
前面讲过运行状态的线程调用阻塞的API(比如阻塞IO)或者等待某个事件或条件,线程就会转换为休眠状态,这个转换时操作系统的线程状态,而Java的线程状态不会改变,还是RUNNABLE状态。JVM并不关心操作系统调度的状态。在JVM看来,等待CPU使用权(操作系统里是处在可执行状态)与等待I/O(操作系统是处在休眠状态),都是等待某个资源,所以都归入了RUNNABLE 状态
当且仅当线程等待synchronized的隐式锁,线程才会从RUNNABLE转换为BLOCKED状态。当获得了synchronized的隐式锁就会从BLOCKED转换为RUNNABLE状态。
2.4 RUNNABLE与WAITING状态转换
从 RUNNABLE到WAITING的状态转换,有如下三种情况:
- 获得synchronized隐式锁的线程,调用无参数Object.wait()时,线程会从 RUNNABLE到WAITING状态转换。
- 调用无参数的Thread.join()方法。当线程t,调用t.join()方法时,执行这个语句的线程会等待t执行完毕,此时该线程从 RUNNABLE转换为WAITING状态,当t执行完毕后,该线程会从WAITING转换为RUNNABLE。
- 调用LockSupport.park()。当调用并发包中的LockSupport.park()方法时,会从 RUNNABLE转换为WAITING状态。当调用LockSupport.unpark(Thread thread) ,线程又会从WAITING转换为RUNNABLE。
2.5.RUNNABLE 与 TIMED_WAITING 的状态转换
当调用带有参数的阻塞方法时,就会从RUNNABLE 转换为 TIMED_WAITING状态。当被唤醒或超时时间到就会从TIMED_WAITING进入RUNNABLE状态。总共有5种情况:
- Thread.sleep(long millis) ;
- Object.wait(long timeout)
- 带超时参数的 Thread.join(long millis) 方法;
- 带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
- 带超时参数的 LockSupport.parkUntil(long deadline) 方法。
2.5 从 RUNNABLE 到 TERMINATED 状态
有如下情况:
- 线程执行完毕。
- 线程执行run方法,抛出异常。
- 主动中断。调用Thread.stop()或者Thread.interrupt()。其中Thread.stop(),已经被标记为@Deprecated,已经不建议使用,因为太暴力,在后续的章节会做详解讲解。
3. 如何查看线程的状态
3.1 通过Thread.getState()查看
在程序中,可以通过调用Thread.getState()查看线程的状态。比如如下代码:
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()-> System.out.println("hello world"));
t.start();
Thread.sleep(10000);
System.out.println(t.getState());
}
}
3.2 通过jstack命令
如上程序,我们通过jps命令或者执行的pid,然后通过命令:jstack pid,可以看到如下输出:
"main" #1 prio=5 os_prio=31 tid=0x00007fd91a003800 nid=0x1803 waiting on condition [0x00007000053af000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at net.zhifou.concurrent.base.ThreadState.main(ThreadState.java:11)
jstack是很强大的命令行工具,不但可以查看线程的状态,还可以查看调用栈、锁等信息,后续章节将做详细讲解。
3.4 其他工具
- Java VisualVM是java提供的可视化工具,可以通过可视化的方式,展现出java的线程栈信息。
- Arthas(阿尔萨斯),是Alibaba开源的Java诊断工具,很好用。这里不做详解,可以从官网获取。