在我们接触编程时,就开始接触各种生命周期,比如对象的生命周期,程序的生命周期等等,对于线程来说也是存在自己的生命周期,而且这也是面试与我们深入了解多线程必备的知识,今天我们主要介绍线程的生命周期及其各种状态的转换。
线程的六种状态
线程的生命周期主要有以下六种状态:
- New(新创建)
- Runnable(可运行)
- Blocked(被阻塞)
- Waiting(等待)
- Timed Waiting(计时等待)
- Terminated(被终止)
在我们程序编码中如果想要确定线程当前的状态,可以通过getState()
方法来获取,同时我们需要注意任何线程在任何时刻都只能是处于一种状态。
New 新建状态
- 首先我们展示一下整个线程状态的转换流程图,下面我们将进行详细的介绍讲解,如下图所示,我们可以直观的看到六种状态的转换,首先左侧上方是
NEW
状态,这是创建新线程的状态,相当于我们new Thread()
的过程。
New
表示线程被创建但尚未启动的状态:当我们用new Thread()
新建一个线程时,如果线程没有开始运行start()
方法,那么线程也就没有开始执行run()
方法里面的代码,那么此时它的状态就是New
。而一旦线程调用了start()
,它的状态就会从New
变成Runnable
,进入到图中绿色的方框
Runnable 可运行状态
-
Java
中的**Runable **
状态对应操作系统线程状态中的两种状态,分别是Running
和Ready
,也就是说,Java
中处于Runnable
状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。 -
所以,如果一个正在运行的线程是
Runnable
状态,当它运行到任务的一半时,执行该线程的CPU
被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是Runnable
,因为它有可能随时被调度回来继续执行任务。
**阻塞状态**
- 上面认识了线程的关键状态
Runnable
,那么接下来我们来看一下下面的三个状态,这三个状态我们可以统称为阻塞状态,它们分别是Blocked(被阻塞)
、Waiting(等待)
、Timed Waiting(计时等待)
.
Blocked 被阻塞状态
- 首先我们来认识一下
Blocked
状态,这是一个相对简单的状态,我们可以通过下面的图示看到,从Runnable
状态进入到Blocked
状态只有一种途径,那么就是当进入到synchronized
代码块中时未能获得相应的monitor
锁(关于monitor
锁我们在之后专门来介绍,这里我们知道synchronized
的实现都是基于monitor
锁的),
- 在右侧我们可以看到,有连接线从
Blocked
状态指向了Runnable
,也只有一种情况,那么就是当线程获得monitor
锁,此时线程就会进入Runnable
状体中参与CPU
资源的抢夺
Waiting 等待状态
上面我们看完阻塞状态,那么接下来我们了解一下 Waiting
状态,对于 Waiting
状态的进入有三种情况,如下图中所示,分别为:
- 当线程中调用了没有设置
Timeout
参数的Object.wait()
方法 - 当线程调用了没有设置
Timeout
参数的Thread.join()
方法 - 当线程调用了
LockSupport.park()
方法
关于
LockSupport.park()
方法,这里说一下,我们通过上面知道Blocked
是针对synchronized monitor
锁的,但是在Java
中实际是有很多其他锁的,比如ReentrantLock
等,在这些锁中,如果线程没有获取到锁则会直接进入Waiting
状态,其实这种本质上它就是执行了LockSupport.park()
方法进入了Waiting
状态
**Blocked **
与**Waiting**
的区别Blocked
是在等待其他线程释放monitor
锁Waiting
则是在等待某个条件,比如join
的线程执行完毕,或者是notify()/notifyAll()
。
Timed Waiting 计时等待状态
- 最后我们来说说这个
Timed Waiting
状态,它与Waiting
状态非常相似,其中的区别只在于是否有时间的限制,在Timed Waiting
状态时会等待超时,之后由系统唤醒,或者也可以提前被通知唤醒如notify
通过上述图我们可以看到在以下情况会让线程进入 Timed Waiting
状态。
- 线程执行了设置了时间参数的
Thread.sleep(long millis)
方法; - 线程执行了设置了时间参数的
Object.wait(long timeout)
方法; - 线程执行了设置了时间参数的
Thread.join(long millis)
方法; - 线程执行了设置了时间参数的
LockSupport.parkNanos(long nanos)
方法和LockSupport.parkUntil(long deadline)
方法。
通过这个我们可以进一步看到它与 waiting 状态的相同
线程状态间转换
上面我们讲了各自状态的特点和运行状态进入相应状态的情况 ,那么接下来我们将来分析各自状态之间的转换,其实主要就是 Blocked
、waiting
、Timed Waiting
三种状态的转换 ,以及他们是如何进入下一状态最终进入 Runnable
Blocked
进入 Runnable
- 想要从
Blocked
状态进入Runnable
状态,我们上面说过必须要线程获得monitor
锁,但是如果想进入其他状态那么就相对比较特殊,因为它是没有超时机制的,也就是不会主动进入。
如下图中紫色加粗表示线路:
Waiting
进入 Runnable
- 只有当执行了
LockSupport.unpark()
,或者join
的线程运行结束,或者被中断时才可以进入Runnable
状态。 - 如下图标注
- 如果通过其他线程调用
notify()
或notifyAll()
来唤醒它,则它会直接进入Blocked
状态,这里大家可能会有疑问,不是应该直接进入Runnable
吗?这里需要注意一点 ,因为唤醒Waiting
线程的线程如果调用notify()
或notifyAll()
,要求必须首先持有该monitor
锁,这也就是我们说的wait()
、notify
必须在synchronized
代码块中。 - 所以处于
Waiting
状态的线程被唤醒时拿不到该锁,就会进入Blocked
状态,直到执行了notify()/notifyAll()
的唤醒它的线程执行完毕并释放monitor
锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从Blocked
状态回到Runnable
状态。
这里大家一定要注意这点,当我们通过 notify 唤醒时,是先进入阻塞状态的 ,再等抢夺到 monitor 锁喉才会进入 Runnable 状态!
**`Timed Waiting` 进入 `Runnable`**
- 同样在
Timed Waiting
中执行notify()
和notifyAll()
也是一样的道理,它们会先进入Blocked
状态,然后抢夺锁成功后,再回到Runnable
状态。
- 但是对于 Timed Waiting 而言,它存在超时机制,也就是说如果超时时间到了那么就会系统自动直接拿到锁,或者当
join
的线程执行结束/调用了LockSupport.unpark()
/被中断等情况都会直接进入Runnable
状态,而不会经历Blocked
状态
Terminated 终止
最后我们来说最后一种状态,Terminated
终止状态,要想进入这个状态有两种可能。
- run() 方法执行完毕,线程正常退出。
- 出现一个没有捕获的异常,终止了 run() 方法,最终导致意外终止。
总结
最后我们说一下再看线程转换的过程中一定要注意两点:
-
线程的状态是按照箭头方向来走的,比如线程从
New
状态是不可以直接进入Blocked
状态的,它需要先经历Runnable
状态。 -
线程生命周期不可逆:一旦进入
Runnable
状态就不能回到New
状态;一旦被终止就不可能再有任何状态的变化。 -
所以一个线程只能有一次
New
和Terminated
状态,只有处于中间状态才可以相互转换。也就是这两个状态不会参与相互转化
本文由AnonyStar 发布,可转载但需声明原文出处。
欢迎关注微信公账号 :云栖简码 获取更多优质文章
更多文章关注笔者博客 :云栖简码 i-code.online