1 前言
上节我们看了线程是如何创建启动的,那么启动以后线程怎么管理呢,这就要看我们线程的状态管理了,这节我们就来看看线程都有哪些状态以及什么操作下会驱使状态的变化流转。
2 状态定义
Java线程有6种状态定义在Thread的子类State,分别是NEW
、RUNNABLE
、BLOCKED
、WAITING
、TIMED_WAITING
、TERMINATED
public enum State { // 初始 NEW, // 可运行 RUNNABLE, // 阻塞 BLOCKED, // 等待 WAITING, // 超时等待 TIMED_WAITING, // 终止 TERMINATED; }
- 初始(
NEW
):新创建了一个线程对象,但还没有调用start()方法时的状态。 - 运行(
RUNNABLE
):Java线程中将操作系统中的就绪(ready
)和运行中(running
)两种状态笼统的称为“可运行”RUNNABLE
状态。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。所以调用了start()方法并不意味这线程会立即执行。 - 阻塞(
BLOCKED
):表示线程阻塞于锁。没有获得锁的状态。等待的是其他线程释放锁。 - 等待(
WAITING
):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断),无期限等待。等待被唤醒。 - 超时等待(
TIMED_WAITING)
:该状态不同于WAITING
,它可以在指定的时间后自行返回,有时间期限的等待,等待一段设置好的时间。 - 终止(
TERMINATED
):表示该线程已经执行完毕。线程正常的执行完毕,会进入到这种状态。或者是run()
方法中出现了异常,线程会意外终止。
3 状态流转
那么接下里我们看看什么样的操作(即方法行为)下,线程的状态会发生变化:
3.1 初始状态
当创建一个线程后,线程就进入了初始状态。
3.2 可运行状态
3.2.1 就绪状态
就绪状态(ready)只是表名线程有资格运行,若调度程序没有挑选到此线程,此线程将永远是就绪状态。
怎样能够进入就绪状态?
(1)当调用线程的start()方法后,此线程即进入就绪状态。
(2)当线程执行完sleep()方法后,线程即进入就绪状态。
(3)其他线程执行Join()方法结束后,线程进入就绪状态。
(4)等待用户输入完毕,某个线程拿到对象锁,线程进入就绪状态。
(5)当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
(6)锁池里的线程拿到对象锁后,进入就绪状态。
3.2.2 运行中状态
运行中状态(running)即线程调度程序从可运行池中选择一个线程执行。
怎样能够进入运行中状态?只能由CPU选择任意一个就绪状态的线程执行,程序员无法手动指定运行哪个线程,这是线程进入运行中状态的唯一方式。
注意: 这里提到“选择哪个线程运行是CPU自己决定的”,这是Java官方给出的答案:The choice is arbitrary and occurs at the discretion of the implementation
。译文:“这种选择是任意的,由执行者自行决定”。
我们简单来看下示例:
public class ThreadTest extends Thread { @Override public void run() { System.out.println("运行Thread线程"); for (int i = 0; i < 80; i++) { } } public static void main(String[] args) { ThreadTest threadClass = new ThreadTest(); System.out.println("start前状态:" + threadClass.getState()); threadClass.start(); System.out.println("start后状态:" + threadClass.getState()); } }
3.3 阻塞状态
BLOCKED或WAITING或TIME_WAITING这三种统称为阻塞状态。因为这三种状态,都让线程进入等待状态,而不再是运行状态,所以统称为阻塞状态。
通俗的讲,就是线程在排队获取锁,类似排队结账要等着。
3.4 等待状态
等待状态即执行了wait()方法后,将锁释放,进入无尽的等待当中,直到被notify()方法唤醒。处于这种状态的线程CPU将不会给其分配执行时间,如果没有显式唤醒,将永远无期限的等待下去。
我们来看下阻塞和等待状态:
public class ThreadTest implements Runnable { @Override public void run() { say(); } public synchronized void say() { try { TimeUnit.SECONDS.sleep(1); // 睡一秒 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ThreadTest task = new ThreadTest(); Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); // 线程1 获取到锁,还没到Sleep所以是RUNNABLE System.out.println("thread1状态:" + thread1.getState()); // 线程2 获取不到锁,进入BLOCKED System.out.println("thread2状态:" + thread2.getState()); TimeUnit.MILLISECONDS.sleep(200); // 线程1 TIMED_WAITING 因为sleep中 System.out.println("thread1状态:" + thread1.getState()); // 线程2 还是获取不到锁,还是BLOCKED System.out.println("thread2状态:" + thread2.getState()); TimeUnit.SECONDS.sleep(2); // 线程1 wait方法 进入WAITING System.out.println("thread1状态:" + thread1.getState()); // 线程2 获取到锁然后wait方法 进入WAITING System.out.println("thread2状态:" + thread2.getState()); } }
3.5 超时等待状态
等待状态跟超时等待状态的区别就是,超时等待可以设置一个超时时间,如果超过了这个时间,没有被唤醒,那就不等了,自动被唤醒。超时等待就是带时间的方法。我们不能控制的就不要纠结了,写代码要在边界内做事,我们可以优化代码,删除不必要的逻辑,来优化线程的执行速度。
3.6 终止状态
(1)当线程run()方法完成时,或者主线程的main()方法完成时,此时线程的状态就变成了终止状态,即便线程对象还存活着,还没有被垃圾回收,但是线程一旦终止,就不能复生。
(2)线程run()方法中抛出了异常,则会进入终止状态。
注意: 如果在一个终止后的线程重新调用start()
,会抛出java.lang.IllegalThreadStateException
异常,说明线程不能死而复生。
4 图示
接下来我们画个图来理解一下线程状态的流转:
(1)只能正序的三个状态
线程只能从NEW到RUNNABLE在到TERMINATED,这是顺序不能倒序。如果还想执行NEW,只能重新创建线程,原来的线程将被JVM回收。
(2)可以逆向执行的三个状态
线程的RUNNABLE到WAITING、RUNNABLE到TIME_WAITING、RUNNABLE到BLOCKED是可以双向执行的。
(3)线程状态不可跳跃执行
线程状态是不能跳跃的,NEW状态是不能跳跃到BLOCKED、WAITING、TIME_WAITING状态执行的,NEW也不能直接跳跃到TERMINATED状态。
5 小结
本节我们主要看了下线程的状态的定义以及状态流转,有理解不对的地方欢迎指正哈。
标签:状态,流转,start,WAITING,线程,就绪,等待 From: https://www.cnblogs.com/kukuxjx/p/17331031.html