文章目录
- 一、JDK1.5之前:5种状态
- 五种状态
- 1、新建
- 2、就绪
- 3、运行
- 4、阻塞
- 5、死亡
- 二、JDK1.5及之后:六种状态
Java语言使用 Thread类及其子类的对象来表示
线程
,在它的一个完整的生命周期中通常要经历如下一些状态。
一、JDK1.5之前:5种状态
线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。
CPU需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。
注意:是一些方法的调用,使得线程从一种状态切换到另外一种状态。
现在说的是方法的执行导致状态的改变,以后会说到状态变化使得一些方法会执行。
五种状态
1、新建
🍰new了一个线程的对象,new完之后这个线程就“新建
”了。
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
此时它和其他Java对象一样,仅仅由JVM为其分配了内存,并初始化了实例变量的值。
此时的线程对象并没有任何线程的动态特征,程序也不会执行它的线程体run()
。
2、就绪
🍰“就绪
”就是CPU可以随时去执行它了。从“新建”到“就绪”需要调用start()
方法。
若是新建完之后不调用start()
方法,这个线程就始终不会被CPU执行。
但是当线程对象调用了start()
方法之后,就不一样了,线程就从新建状态转为就绪状态。
JVM会为其创建方法调用栈和程序计数器,当然,处于这个状态中的线程并没有开始运行,只是表示已具备了运行的条件,随时可以被调度。至于什么时候被调度,取决于JVM里线程调度器的调度。
🗳️注意
程序只能对新建状态的线程调用start()
,并且只能调用一次。
如果对非新建状态的线程,如已启动的线程或已死亡的线程调用start()
都会报错IllegalThreadStateException
异常。
3、运行
🍰位于“就绪态”的线程被CPU选择执行,这个线程就处于了“运行态
”。
如果处于就绪状态的线程获得了CPU资源时,开始执行run()
方法的线程体代码,则该线程处于运行状态。
如果计算机只有一个CPU核心,在任何时刻只有一个线程处于运行状态;如果计算机有多个核心,将会有多个线程并行(Parallel)执行。
当然,美好的时光总是短暂的,而且CPU讲究雨露均沾。
对于抢占式策略
的系统而言,系统会给每个可执行的线程一个小时间段来处理任务,当该时间用完,系统会剥夺该线程所占用的资源,让其回到就绪状态等待下一次被调度。此时其他线程将获得执行机会,而在选择下一个线程时,系统会适当考虑线程的优先级。
“运行”是我们希望看到的,就是执行run()方法了。
🗳️注意
①线程获得CPU的执行权:从“就绪”到“运行”。(当CPU执行到这个线程,而这个线程又刚好是就绪状态,这个线程就会被CPU执行)
②线程失去了CPU的执行权:从“运行”到“就绪”。(当线程主动调用yield()方法,就是指这个线程主动要去释放CPU执行权,这个线程会进入就绪状态,注意不是阻塞状态)
4、阻塞
🍰当在运行过程中的线程遇到如下情况时,会让出 CPU 并临时中止自己的执行,进入阻塞状态:
- 线程调用了
sleep()
方法,主动放弃所占用的CPU资源; - 线程试图获取一个同步监视器,但该同步监视器正被其他线程持有;
- 线程执行过程中,同步监视器调用了
wait()
,让它等待某个通知(notify); - 线程执行过程中,同步监视器调用了
wait(time)
; - 线程执行过程中,遇到了其他线程对象的加塞(join);
- 线程被调用
suspend
方法被挂起(已过时,因为容易发生死锁)
当前正在执行的线程被阻塞后,其他线程就有机会执行了。
针对如上情况,当发生如下情况时会解除阻塞,让该线程重新进入就绪状态,等待线程调度器再次调度它:
- 线程的
sleep()
时间到; - 线程成功获得了同步监视器;
- 线程等到了通知(notify);
- 线程
wait
的时间到了; - 加塞的线程结束了;
- 被挂起的线程又被调用了
resume
恢复方法(已过时,因为容易发生死锁)
当线程处于“阻塞”状态了,不会一直阻塞,一定会到就绪状态,否则程序就会有问题,他需要回到 “就绪”状态,然后被CPU分配并执行,最终“死亡”。
5、死亡
线程会以以下三种方式之一结束,结束后的线程就处于死亡状态:
run()
方法执行完成,线程正常结束;- 线程执行过程中抛出了一个未捕获的异常(Exception)或错误(Error);
- 直接调用该线程的
stop()
来结束该线程(已过时)
“死亡”状态无法回到“运行”状态。
二、JDK1.5及之后:六种状态
在java.lang.Thread.State
的枚举类中这样定义:
public enum State { //里面的对象表示线程的生命周期
NEW, //新建
RUNNABLE, //就绪+运行
BLOCKED, //阻塞
WAITING, //等待
TIMED_WAITING, //有指定时间的等待
TERMINATED; //死亡
}
State是Thread类中的内部类,还是一个枚举类。
🍰详细介绍
<1> NEW(新建)
:线程刚被创建,但是并未启动。还没调用start方法。
还是JDK1.5之前版本的“新建”。
<2> RUNNABLE(可运行)
:这里没有区分就绪和运行状态。
因为对于Java对象来说,只能标记为可运行,至于什么时候运行,不是JVM来控制的了,是OS来进行调度的,而且时间非常短暂,因此对于Java对象的状态来说,无法区分。
“就绪”又可以叫“准备”,“准备”和“运行”合在一起,称为Runnable。
这是一个可以运行的状态,不区分是准备好了还是正在运行了。
<3> Teminated(被终止)
:表明此线程已经结束生命周期,终止运行。
还是JDK1.5之前版本的“死亡”。
<4> 重点说明,根据Thread.State的定义,根据不同方法的调用,将阻塞状态分为三种:BLOCKED
、WAITING
、TIMED_WAITING
。
BLOCKED(锁阻塞)
:在API中的介绍为:一个正在阻塞、等待一个监视器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行机会。- 比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
TIMED_WAITING(计时等待)
:在API中的介绍为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。- 当前线程执行过程中遇到Thread类的
sleep
或join
,Object类的wait
,LockSupport类的park
方法,并且在调用这些方法时,设置了时间
,那么当前线程会进入TIMED_WAITING,直到时间到,或被中断。
- 当前线程执行过程中遇到Thread类的
WAITING(无限等待)
:在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。- 当前线程执行过程中遇到遇到Object类的
wait
,Thread类的join
,LockSupport类的park
方法,并且在调用这些方法时,没有指定时间
,那么当前线程会进入WAITING状态,直到被唤醒。- 通过Object类的wait进入WAITING状态的要有Object的notify/notifyAll唤醒;
- 通过Condition的await进入WAITING状态的要有Condition的signal方法唤醒;
- 通过LockSupport类的park方法进入WAITING状态的要有LockSupport类的unpark方法唤醒;
- 通过Thread类的join进入WAITING状态,只有调用join方法的线程对象结束才能让当前线程恢复;
- 当前线程执行过程中遇到遇到Object类的
若wait()小括号里面没有写时间,这个就是无限等待,只能被notify()。
若wait()小括号里面写了时间,到了时间就会结束等待,叫做指定时间的等待。
🎲说明:当从WAITING或TIMED_WAITING恢复到Runnable状态时,如果发现当前线程没有得到监视器锁,那么会立刻转入BLOCKED状态。
我们在翻阅API的时候会发现Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的,比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。
这种带参的方法,其实是一种倒计时操作,相当于我们生活中的小闹钟,我们设定好时间,到时通知,可是如果提前得到(唤醒)通知,那么设定好时间在通知也就显得多此一举了,那么这种设计方案其实是一举两得。
如果没有得到(唤醒)通知,那么线程就处于Timed Waiting状态,直到倒计时完毕自动醒来;如果在倒计时期间得到(唤醒)通知,那么线程从Timed Waiting状态立刻唤醒。
【举例】
public class ThreadStateTest {
public static void main(String[] args) throws InterruptedException {
SubThread t = new SubThread();
System.out.println(t.getName() + " 状态 " + t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()) {
System.out.println(t.getName() + " 状态 " + t.getState());
Thread.sleep(500);
}
System.out.println(t.getName() + " 状态 " + t.getState());
}
}
class SubThread extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
System.out.println("打印:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
命令行演示:
关于这些方法,现在了解即可,后面慢慢介绍。