前言
目前正在出一个Java多线程专题
长期系列教程,从入门到进阶含源码解读
, 篇幅会较多, 喜欢的话,给个关注❤️ ~
线程状态的转换
如题,这也是我们面试中常被问到的。我们先看一下系统中线程状态是如何转换的。之前我们在讲进程和线程
的概述时提到过,线程
可以被视为轻量级
的进程。所以在系统调度过程中,他们的状态转换是一致的。
首先最开始是创建阶段 NEW
, 下一个阶段是就绪阶段 Ready
, 紧接着就是调度转为执行阶段 Running
, 执行的这个过程中,可能会被打断又变为Ready
阶段,还有可能一些IO
阻塞事件有又变为等待阶段 Waiting
, 这个阶段又转换为 Ready
阶段,当退出时,会转化为结束阶段 Terminated
。说的有点抽象,大家可以对照下图理解一下。
操作系统线程主要有以下三个状态:
- 就绪状态(ready):线程正在等待使用CPU,经调度程序调用之后可进入running状态。
- 执行状态(running):线程正在使用CPU。
- 等待状态(waiting): 线程经过等待事件的调用或者正在等待其他资源(如I/O)。
Java中的线程状态
我们可以通过Thread.State
查看它的状态
枚举,一共提供了6
种状态
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; } 复制代码
下面,我们一个个看
NEW
当前状态下的线程表示还未启动
,也就是没有执行start
, 我们来验证一下,可以通过getState
来获取线程的状态
public class ThreadStateTest { public static void main(String[] args) { Thread t = new Thread(() -> { System.out.println("hello"); }); System.out.println(t.getState()); t.start(); System.out.println(t.getState()); } } 复制代码
实际输出:
NEW RUNNABLE hello 复制代码
我们发现在start
调用前后,线程状态是不一致的,未启动下是NEW
RUNNABLE
可运行
线程的线程状态,处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自操作系统的其他资源。这个状态其实包含了操作系统的两种状态,Ready和Running
BLOCKED
线程阻塞等待监视器锁的线程状态。这个怎么去理解呢❓ 打个比方,我们去银行办业务,首先我们要去取号排队,只有排在你前面的人业务办完了,下面才轮到你。下面我们用代码来验证一下,首先我们看一下没加锁的情况下的状态
public static void main(String[] args) throws InterruptedException { Bank bank = new Bank(); Thread t = new Thread(() -> { try { bank.doSomething("a"); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t1 = new Thread(() -> { try { bank.doSomething("b"); } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); t1.start(); Thread.sleep(1000); System.out.println("t----->" + t.getState()); System.out.println("t1---->" + t1.getState()); Thread.sleep(3000); System.out.println("t----->" + t.getState()); System.out.println("t1---->" + t1.getState()); Thread.sleep(3000); System.out.println("t----->" + t.getState()); System.out.println("t1---->" + t1.getState()); } 复制代码
class Bank { public void doSomething(String name) throws InterruptedException { System.out.println(name); Thread.sleep(3000); } } 复制代码
输出:
b a t----->TIMED_WAITING t1---->TIMED_WAITING t----->TERMINATED t1---->TERMINATED t----->TERMINATED t1---->TERMINATED 复制代码
发现几乎都是并发执行的,下面我们看下加锁
下是怎么的状态❓
class Bank { public void doSomething(String name) throws InterruptedException { synchronized(this) { System.out.println(name); Thread.sleep(3000); } } } 复制代码
这里使用了synchronized
同步锁,意思是同一时刻只允许一个线程去执行,具体用法后边会给大家讲,可以先留个印象,看下实际输出:
a t----->TIMED_WAITING t1---->BLOCKED b t----->TERMINATED t1---->TIMED_WAITING t----->TERMINATED t1---->TERMINATED 复制代码
我们可以很清楚的看到两个线程的状态转换
WAITING & TIMED_WAITING
WAITING
处于等待状态的线程正在等待另一个线程执行特定操作。上面的例子,我们看到TIMED_WAITING
,超时等待状态。线程等待一个具体的时间,时间到后会被自动唤醒,可以看到调用了Thread.sleep()
, 那么还有哪些方法可以让线程处于超时等待状态呢❓
- Object.wait(long timeout)
class Bank { public void doSomething(String name) throws InterruptedException { synchronized(this) { if("a".equals(name)) { this.wait(3000); } System.out.println(name); } } } 复制代码
输出:
b t----->TIMED_WAITING t1---->TERMINATED a t----->TERMINATED t1---->TERMINATED t----->TERMINATED t1---->TERMINATED 复制代码
通过输出可以发现,t线程处于超时等待
Thread.join(long millis)
:等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("b"); }); t1.start(); t1.join(60); System.out.println(t1.getState()); } 复制代码
输出:
TIMED_WAITING b 复制代码
发现指定了60 ms
但线程内部实际执行大于`6000 ms,所以
t1```会处于超时等待。
LockSupport.parkNanos(long nanos)
: 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;
public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { LockSupport.parkNanos(3000*6000*4000L); System.out.println("a"); }); t.start(); Thread.sleep(3000); System.out.println(t.getState()); } 复制代码
输出:
TIMED_WAITING 复制代码
LockSupport.parkUntil(long deadline
):同上,也是禁止线程进行调度指定时间;
public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { LockSupport.parkUntil(System.currentTimeMillis() + 10 * 1000L); System.out.println("a"); }); t.start(); Thread.sleep(6000); System.out.println(t.getState()); } 复制代码
注意这里传的时间,看一下输出:
TIMED_WAITING a 复制代码
调用以下方法,会导致线程处于WAITING
,这里就不给大家一一演示了,差别其实就是是否指定了超时时间
,大家可以自己试一下,可以参考上边。
- Object.wait没有超时
- 没有超时的Thread.join
- LockSupport.park
TERMINATED
终止状态。此时线程已执行完毕。这个很好理解,之前给大家演示过了
Java线程状态转换
通过上面的了解之后,我们总结一下线程状态的转换
- 首先是
NEW
阶段 - 调用
start
进入Runable
阶段 - 如果期间调用
wait(),join(),park()
,线程会进去WAITING
阶段,被唤醒后进入Runable
阶段,可以通过notify(),nofityAll(),unpark()
方式唤醒 - 如果期间调用
sleep(time), wait(time), parkNanos(time), parkUntil(time),join(time)
,线程会进入TIME_WAITING
,被唤醒后进入Runable
阶段,可以通过notify(),nofityAll(),unpark()
方式唤醒 - 如果线程处于
等待锁
状态,此时线程会进去BLOCK
阶段,成功
拿到锁后进入Runable
阶段 - 最终运行结束会进入
TERMIATED
阶段,也就是结束阶段
结束语
关于notify
的用法,我们下节给大家讲,此处先有个印象,因为这个地方涉及到线程之间的通信了。本篇内容到这里就结束了, 大家自己一定要多去理解,不要去背, 下期给大家讲讲线程之间如何进行通信
~