一天分万态,立地看忘回。 --《夏云》
1. 6种状态
Java语言线程的生命周期中主要有6种状态,分别是New(新创建)、Runnable(可运行)、Blocked(被阻塞)、Waiting(等待)、TimedWaiting(计时等待)、Terminated(被终止)。可以通过调用getState()方法,查看线程当前所处的状态,并且线程在同一时间只能处于一种状态。6种状态的转换图如下:
下面会逐个展开介绍各个状态。
2. New
New状态代表线程已经被创建但是尚未启动:使用 new Thread() 创建了一个线程,但是并没有调用 start() 方法,此时线程就处于 New 状态。一旦调用 start() 方法后,线程的状态就会由New状态转变为 Runnable 状态。
3. Runnable
Java 中线程的 Runnable 状态对应到操作系统上是有 Ready 和 Running 两种状态。如果线程是处在等待获取 CPU 资源的状态,则是处于 Ready 状态的。在获取到 CPU 资源后执行所需执行的任务时便是处于 Running 状态了。
一个线程若是处在Runnable状态时,其执行的任务并没有执行完成,执行该任务的 CPU 被调度去执行其它任务了,此时线程的状态仍然会是 Runnable ,其让出的 CPU 时间片随时有可能都会再次被获取到,然后继续执行任务。
4. 阻塞状态
Java 中的阻塞状态并不只是有 Blocked 这一种状态,而是有 Waiting、Timed Waiting 和 Blocked 这三种不同的状态,下面就详细看一下这三种状态。
4.1 Blocked
上方的流程图可以清晰的看出,线程从 Runnable 状态进入 Blocked 状态只有一种可能,就是 synchronized 关键字所保护的代码(无论 synchronized 是添加在同步代码块上,还是添加在方法签名上)没能够争抢到 monitor 锁。
处于 Blocked 状态的线程在获得 monitor 锁后会重新进入 Runnable 状态。
4.2 Waiting
刚已经强调了线程进入 Blocked 状态只是在 synchronized 关键字获取 monitor 锁失败时才会发生,但是 Java 中的锁可并不是只有这一种哦,还存在着其他各种类型的锁。比如 ReentrantLock ,若是线程在获取 ReentrantLock 锁失败时便会进入 Waiting 状态,其底层是通过调用了LockSupport.park() 方法来实现的,类似的还有 Object.wait() 和 Thread.join() 。
简单汇总一下,线程进入 Runnable 状态在以下3种情况下才会发生:
- 调用了 Object.wait() 方法;
- 调用了 Thread.join() 方法;
- 调用了 LockSupport.park() 方法。
不同于 Blocked 状态实际上是在等待其他线程释放 monitor 锁,Waiting 状态本质上是在等待某个结束的信号或条件,比如 join 线程已经执行完毕或者是被中断了,再比如 Object.notify() 或 Object.notifyAll()等等。
Waiting 状态并没有等待的时限,无论多久都不会自动恢复,只有执行了 LockSupport.unpark() 或 join 的线程运行结束或被中断后,才可以重新进入 Runnable 状态。
若是有其他的线程调用 Object.notify() 或 Object.notifyAll() 来唤醒处于 Waiting 状态的线程,并不会直接进入 Runnable 状态,而是会先进入 Blocked 状态。这是因为唤醒处于 Waiting 状态线程的线程需要先持有该 monitor 锁,故而刚被唤醒的 Waiting 线程是无法直接获取到该 monitor 锁的,便会进入 Blocked 状态,直到调用 Object.notify() 或 Object.notifyAll() 方法的线程执行完唤醒操作后,释放了 monitor 锁,被唤醒的 Waiting 线程才能去竞争这把 monitor 锁,如果抢夺到了 monitor 锁才会从 Blocked 状态进入 Runnable 状态。
4.3 Timed Waiting
Timed Waiting(限时等待)的状态同 Waiting(等待)的状态时基本类似的,唯一的区别就是 Timed Waiting 设置了等待的超时时长,若是超过 timeout 时间后线程未被其他条件唤醒,也会被唤醒并结束等待。
具体进入 Timed Waiting 的方式有如下几种:
- 调用 Thread.sleep(long millis) 方法;
- 调用 Object.wait(long timeout) 方法;
- 调用 Thread.join(long millis) 方法;
- 调用 LockSupport.parkNanos(long nanos) 方法或 LockSupport.parkUntil(long deadline) 方法。
Timed Waiting 状态的线程,在通过 Object.notify() 或 Object.notifyAll() 进行唤醒的情况同 Waiting 状态被通过这两种方式唤醒的处理方式是相同的,都是会先进入 Blocked 状态,竞争到 monitor 锁后再进入 Runnable 状态。
如果是 timeout 超时时间到了并且可直接获取到锁,join 线程运行结束或被中断,调用了 LockSupport.unpark() 这3种方式,Timed Waiting 状态的线程会直接进入 Runnable 状态,而不会经历 Blocked 的过程。
5. Terminated
线程的最后一种状态便是 Terminated 了,进入这种状态的条件有如下2种:
- run() 方法执行完毕,线程正常退出;
- 线程在执行过程中出现了一个没有补货的异常,终止了 run() 方法的执行,最终导致整个线程的意外退出。
6. 注意事项
以上就是对线程的6种状态及其之间相互转换的说明,还有两个小的注意事项也需要注意一下:
- 线程状态的转换是必须遵循一定规律的,有些状态之间是不能直接可达的,比如 New 状态是不可能直接进入 Blocked 状态的,而是需要先经历 Runnable 状态;
- 线程的整个生命周期是不可逆的:New 状态的线程一旦 start() 进入 Runnable 状态后便不可能再回到 New 状态;同样的,线程的状态一旦到达 Terminated 状态,便不可能再进入到其他任何状态;所有的状态转换,只能是介于 New 和 Terminated 之间的中间状态的转换。