简介
Java线程有6种状态,分别是NEW
、RUNNABLE
、BLOCKED
、WAITING
、TIMED_WAITING
、TERMINATED
。本文讲解线程状态变化的流程以及用代码演示通过调用哪些方法可以使线程改变状态。
一.简述6种线程状态定义
将线程的各种状态定义列出在此,方便大家查阅,下文再详细讲解。
-
初始(
NEW
):新创建了一个线程对象,但还没有调用start()方法时的状态。 -
运行(
RUNNABLE
):Java线程中将操作系统中的就绪(ready
)和运行中(running
)两种状态笼统的称为“可运行”RUNNABLE
状态。
线程对象创建后,其他线程(比如main
线程)调用了该对象的start()
方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready
)。就绪状态的线程在获得CPU时间片后变为运行中状态(running
)。所以调用了start()
方法并不意味这线程会立即执行。 -
阻塞(
BLOCKED
):表示线程阻塞于锁。仅在Synchronized
代码块中,且没有获得锁的状态。
等待的是其他线程释放排他锁。 -
等待(
WAITING
):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
无期限等待。等待被唤醒。 -
超时等待(
TIMED_WAITING)
:该状态不同于WAITING
,它可以在指定的时间后自行返回。
有时间期限的等待。等待一段设置好的时间。 -
终止(
TERMINATED
):表示该线程已经执行完毕。
线程正常的执行完毕,会进入到这种状态。或者是run()
方法中出现了异常,线程会意外终止。
二.线程状态详细解析
1.初始状态
当创建一个线程后,线程就进入了初始状态。如何创建线程,参考文章:《Java官方文档创建线程的两种方式及优点和缺点分析》。
2.1运行状态(就绪状态)
就绪状态(ready)只是表名线程有资格运行,若调度程序没有挑选到此线程,此线程将永远是就绪状态。
怎样能够进入就绪状态?
①当调用线程的start()
方法后,此线程即进入就绪状态。
②当线程执行完sleep()
方法后,线程即进入就绪状态。
③其他线程执行Join()
方法结束后,线程进入就绪状态。
④等待用户输入完毕,某个线程拿到对象锁,线程进入就绪状态。
⑤当前线程时间片用完了,调用当前线程的yield()
方法,当前线程进入就绪状态。
⑥锁池里的线程拿到对象锁后,进入就绪状态。
2.2.运行状态(运行中状态)
运行中状态(running)即线程调度程序从可运行池中选择一个线程执行。
怎样能够进入运行中状态?
只能由CPU选择任意一个就绪状态的线程执行,程序员无法手动指定运行哪个线程,这是线程进入运行中状态的唯一方式。
注意: 这里提到“选择哪个线程运行是CPU自己决定的”,这是Java官方给出的答案:The choice is arbitrary and occurs at the discretion of the implementation
。译文:“这种选择是任意的,由执行者自行决定”。
代码演示NEW和RUNNABLE状态
public class ShowThreadRunnableStatus implements Runnable {
public static void main(String[] args) {
Thread thread = new Thread(new ShowThreadRunnableStatus());
System.out.println("创建完线程的状态:" + thread.getState());
thread.start();
System.out.println("启动后线程的状态:" + thread.getState());
try {
// 线程执行2毫秒以后,打印一下状态
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行中的线程状态:" + thread.getState());
}
@Override
public void run() {
for (int i = 0; i < 800; i++) {
System.out.println(i);
}
}
}
运行结果:
创建完线程的状态:NEW
启动后线程的状态:RUNNABLE
0
1
2
3
打印结果省略……
263
264
265
线程执行中的线程状态:RUNNABLE
266
267
268
打印结果省略……
3.阻塞状态
经常听到“线程阻塞”这个名词,那到底什么是阻塞呢?
BLOCKED或WAITING或TIME_WAITING这三种统称为堵塞状态。因为这三种状态,都让线程进入等待状态,而不再是运行状态,所以统称为堵塞状态。
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,即还没有真正运行Synchronized修饰的代码时的状态。
通俗的讲,就是线程想要执行Synchronized代码块内容,但却没有拿到锁,此时等待时的状态就是堵塞状态。
生活例子:
就像是排队购物时,马上到你但还没到你的状态,就是堵塞状态。你被堵塞在购物窗口之前,当轮到你购买付款时,那就进入了运行状态。
4.等待状态
等待状态即执行了`wait()`方法后,将锁释放,进入无尽的等待当中,直到被`notify()`方法唤醒。
处于这种状态的线程CPU将不会给其分配执行时间,如果没有显式唤醒,将永远无期限的等待下去。
生活例子:
就像是面试官在面试结束后,然你回去等消息。如果面试官不主动联系你,那就说明你已经没有机会了,此时你就只能无期限的等下去。面试官一般不会打电话告诉你,你没有通过面试。此时对这个岗位的面试,就陷入了无尽的等待,这就是等待状态。
代码展示WAITING状态
public class ShowThreadWaitingStatus implements Runnable {
public static void main(String[] args) {
ShowThreadWaitingStatus runnable = new ShowThreadWaitingStatus();
Thread thread1 = new Thread(runnable);
thread1.start();
Thread thread2 = new Thread(runnable);
thread2.start();
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印出TIME_WAITING,原因是:Thread.sleep(1000);
System.out.println("线程1的状态:" + thread1.getState());
// 打印出BLOCKED,因为线程2拿不到method的方法锁
System.out.println("线程2的状态:" + thread2.getState());
try {
Thread.sleep(1400);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印出WAITING,因为wait()方法,使线程进入到WAITING状态
System.out.println("线程1的状态:" + thread1.getState());
// 打印出TIME_WAITING,因为Thread.sleep(1400)方法,使线程进入到TIME_WAITING状态
System.out.println("线程2的状态:" + thread2.getState());
}
@Override
public void run() {
method();
}
/**
*
*/
private synchronized void method() {
try {
Thread.sleep(1000);
wait();
//System.out.println(Thread.currentThread().getName() + ",状态是:" + Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:(在不同的JVM机器上结果可能会不同)
线程1的状态:TIMED_WAITING
线程2的状态:BLOCKED
线程1的状态:WAITING
线程2的状态:TIMED_WAITING
5.超时状态
等待状态即执行了`wait(timeout)`方法后,将锁释放,进入等待当中,直到被`notify()`方法唤醒或者时间超过timeout设置的毫秒数后,自动唤醒。
等待状态跟超时等待状态的区别就是,超时等待可以设置一个超时时间,如果超过了这个时间,没有被唤醒,那就不等了,自动被唤醒。
线程何时被唤醒我们无法控制,但我们可以修改和优化线程执行时间。
我们不能控制的就不要纠结了,写代码要在边界内做事,我们可以优化代码,删除不必要的逻辑,来优化线程的执行速度。
生活例子:
就像被老师罚站,如果数学老师有急事离开了教室但忘了取消对你的惩罚,那你就只能一直被罚站,直到下课铃响起,即便数学老师没有让你坐下,你也可以自行解除封印,坐着学习下一节的语文课,这就是过了指定的时间后,自行唤醒。
代码演示TIME_WAITING和BLOCKED状态
public class ShowThreadBlockedStatus implements Runnable {
public static void main(String[] args) {
ShowThreadBlockedStatus runnable = new ShowThreadBlockedStatus();
Thread thread1 = new Thread(runnable);
thread1.start();
Thread thread2 = new Thread(runnable);
thread2.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程1的状态:TIMED_WAITING,因为:Thread.sleep(1000)
System.out.println("线程1的状态:" + thread1.getState());
// 打印出BLOCKED,因为拿不到method的方法锁
System.out.println("线程2的状态:" + thread2.getState());
}
@Override
public void run() {
method();
}
/**
*
*/
private synchronized void method() {
try {
Thread.sleep(1000);
//System.out.println(Thread.currentThread().getName() + ",状态是:" + Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
线程1的状态:TIMED_WAITING
线程2的状态:BLOCKED
6.终止状态
如何进入终止状态
①.当线程run()
方法完成时,或者主线程的main()
方法完成时,此时线程的状态就变成了终止状态,即便线程对象还存活着,还没有被垃圾回收,但是线程一旦终止,就不能复生。
注意: 如果在一个终止后的线程重新调用start()
,会抛出java.lang.IllegalThreadStateException
异常,说明线程不能死而复生。
②线程run()
方法中抛出了异常,则会进入终止状态。
三.线程状态的变化和执行顺序
1.只能正序的三个状态
线程只能从NEW
到RUNNABLE
在到TERMINATED
,这是顺序不能倒序(上图最左侧的一列)。如果还想执行NEW,只能重新创建线程,原来的线程将被JVM回收。
2.可以逆向执行的三个状态
线程的RUNNABLE
到WAITING
、RUNNABLE
到TIME_WAITING
、RUNNABLE
到BLOCKED
是可以双向执行的。
3.线程状态不可跳跃执行
线程状态是不能跳跃的,NEW
状态是不能跳跃到BLOCKED
、WAITING
、TIME_WAITING
状态执行的(),NEW
也不能直接跳跃到TERMINATED
状态。
总结
本文详细讲解了线程的6种状态各自的定义,以及从代码层面演示并分析了各种状态的含义,讲解了线程执行的流程以及注意事项。涉及到线程的其他知识,请见下发补充。喜欢本文请点赞、收藏、关注。
参考资料补充:
关于多线程、synchronized关键字
、wait()
、notify()
方法的系列教程,请参考以下文章:
《Java中synchronized实现类锁的两种方式及原理解析》
《Java中synchronized实现对象锁的两种方式及原理解析》
《Java多线程wait()和notify()系列方法使用教程》
《Java中Synchronized的可重入性和不可中断性的分析和代码验证》