总算把线程六种状态的转换说清楚了! 线程的六种状态 New 新建状态 Runnable 可运行状态 Blocked 被阻塞状态 Waiting 等待状态 Timed Waiting 计时等待状态 线程状态间转换 Terminated 终止 总结

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结




在我们接触编程时,就开始接触各种生命周期,比如对象的生命周期,程序的生命周期等等,对于线程来说也是存在自己的生命周期,而且这也是面试与我们深入了解多线程必备的知识,今天我们主要介绍线程的生命周期及其各种状态的转换。

线程的生命周期主要有以下六种状态:

  • New(新创建)
  • Runnable(可运行)
  • Blocked(被阻塞)
  • Waiting(等待)
  • Timed Waiting(计时等待)
  • Terminated(被终止)


在我们程序编码中如果想要确定线程当前的状态,可以通过getState()方法来获取,同时我们需要注意任何线程在任何时刻都只能是处于一种状态。


New 新建状态

  • 首先我们展示一下整个线程状态的转换流程图,下面我们将进行详细的介绍讲解,如下图所示,我们可以直观的看到六种状态的转换,首先左侧上方是 NEW 状态,这是创建新线程的状态,相当于我们 new Thread() 的过程。

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结

  • 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 锁的),


总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结

  • 在右侧我们可以看到,有连接线从 Blocked 状态指向了 Runnable ,也只有一种情况,那么就是当线程获得 monitor 锁,此时线程就会进入 Runnable 状体中参与 CPU 资源的抢夺

Waiting 等待状态


上面我们看完阻塞状态,那么接下来我们了解一下 Waiting 状态,对于 Waiting 状态的进入有三种情况,如下图中所示,分别为:

  • 当线程中调用了没有设置 Timeout 参数的 Object.wait() 方法
  • 当线程调用了没有设置 Timeout 参数的 Thread.join() 方法
  • 当线程调用了 LockSupport.park() 方法

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结

关于 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

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结


通过上述图我们可以看到在以下情况会让线程进入 Timed Waiting 状态。

  • 线程执行了设置了时间参数的 Thread.sleep(long millis) 方法;
  • 线程执行了设置了时间参数的 Object.wait(long timeout) 方法;
  • 线程执行了设置了时间参数的 Thread.join(long millis) 方法;
  • 线程执行了设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。

通过这个我们可以进一步看到它与 waiting 状态的相同

线程状态间转换

上面我们讲了各自状态的特点和运行状态进入相应状态的情况 ,那么接下来我们将来分析各自状态之间的转换,其实主要就是 BlockedwaitingTimed Waiting 三种状态的转换 ,以及他们是如何进入下一状态最终进入 Runnable


Blocked 进入 Runnable

  • 想要从 Blocked 状态进入 Runnable 状态,我们上面说过必须要线程获得 monitor 锁,但是如果想进入其他状态那么就相对比较特殊,因为它是没有超时机制的,也就是不会主动进入。

如下图中紫色加粗表示线路:
总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结


Waiting 进入 Runnable

  • 只有当执行了 LockSupport.unpark(),或者 join 的线程运行结束,或者被中断时才可以进入 Runnable 状态。
  • 如下图标注

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结

  • 如果通过其他线程调用 notify()notifyAll()来唤醒它,则它会直接进入 Blocked 状态,这里大家可能会有疑问,不是应该直接进入 Runnable 吗?这里需要注意一点 ,因为唤醒 Waiting 线程的线程如果调用 notify()notifyAll(),要求必须首先持有该 monitor 锁,这也就是我们说的 wait()notify 必须在 synchronized 代码块中。
  • 所以处于 Waiting 状态的线程被唤醒时拿不到该锁,就会进入 Blocked 状态,直到执行了 notify()/notifyAll() 的唤醒它的线程执行完毕并释放 monitor 锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从 Blocked 状态回到 Runnable 状态。

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结

这里大家一定要注意这点,当我们通过 notify 唤醒时,是先进入阻塞状态的 ,再等抢夺到 monitor 锁喉才会进入 Runnable 状态!



**`Timed Waiting` 进入 `Runnable`**
  • 同样在 Timed Waiting 中执行 notify()notifyAll() 也是一样的道理,它们会先进入 Blocked 状态,然后抢夺锁成功后,再回到 Runnable 状态。

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结

  • 但是对于 Timed Waiting 而言,它存在超时机制,也就是说如果超时时间到了那么就会系统自动直接拿到锁,或者当 join 的线程执行结束/调用了LockSupport.unpark()/被中断等情况都会直接进入 Runnable 状态,而不会经历 Blocked 状态

总算把线程六种状态的转换说清楚了!
线程的六种状态
New 新建状态
Runnable 可运行状态
Blocked 被阻塞状态
Waiting 等待状态
Timed Waiting 计时等待状态
线程状态间转换
Terminated 终止
总结

Terminated 终止


最后我们来说最后一种状态,Terminated 终止状态,要想进入这个状态有两种可能。

  • run() 方法执行完毕,线程正常退出。
  • 出现一个没有捕获的异常,终止了 run() 方法,最终导致意外终止。

总结

最后我们说一下再看线程转换的过程中一定要注意两点:

  • 线程的状态是按照箭头方向来走的,比如线程从 New 状态是不可以直接进入 Blocked 状态的,它需要先经历 Runnable 状态。

  • 线程生命周期不可逆:一旦进入 Runnable 状态就不能回到 New 状态;一旦被终止就不可能再有任何状态的变化。

  • 所以一个线程只能有一次 New Terminated 状态,只有处于中间状态才可以相互转换。也就是这两个状态不会参与相互转化



本文由AnonyStar 发布,可转载但需声明原文出处。
欢迎关注微信公账号 :云栖简码 获取更多优质文章
更多文章关注笔者博客 :云栖简码 i-code.online