Java多线程--线程的生命周期

文章目录

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枚举类中这样定义:

java 复制代码
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 的定义,根据不同方法的调用,将阻塞状态分为三种BLOCKEDWAITINGTIMED_WAITING

  • BLOCKED(锁阻塞):在API中的介绍为:一个正在阻塞、等待一个监视器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行机会。
    • 比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
  • TIMED_WAITING(计时等待):在API中的介绍为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。
    • 当前线程执行过程中遇到Thread类的sleepjoin,Object类的wait,LockSupport类的park方法,并且在调用这些方法时,设置了时间,那么当前线程会进入TIMED_WAITING,直到时间到,或被中断。
  • 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方法的线程对象结束才能让当前线程恢复;

若wait()小括号里面没有写时间,这个就是无限等待,只能被notify()。

若wait()小括号里面写了时间,到了时间就会结束等待,叫做指定时间的等待。


🎲说明:当从WAITING或TIMED_WAITING恢复到Runnable状态时,如果发现当前线程没有得到监视器锁,那么会立刻转入BLOCKED状态。

我们在翻阅API的时候会发现Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的,比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。

这种带参的方法,其实是一种倒计时操作,相当于我们生活中的小闹钟,我们设定好时间,到时通知,可是如果提前得到(唤醒)通知,那么设定好时间在通知也就显得多此一举了,那么这种设计方案其实是一举两得。

如果没有得到(唤醒)通知,那么线程就处于Timed Waiting状态,直到倒计时完毕自动醒来;如果在倒计时期间得到(唤醒)通知,那么线程从Timed Waiting状态立刻唤醒。


【举例】

java 复制代码
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;
        }
    }
}

命令行演示:

关于这些方法,现在了解即可,后面慢慢介绍。

相关推荐
百事老饼干13 分钟前
Java[面试题]-真实面试
java·开发语言·面试
customer0820 分钟前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
2402_8575893630 分钟前
SpringBoot框架:作业管理技术新解
java·spring boot·后端
HBryce2434 分钟前
缓存-基础概念
java·缓存
一只爱打拳的程序猿1 小时前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
杨荧1 小时前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
minDuck1 小时前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
为将者,自当识天晓地。1 小时前
c++多线程
java·开发语言
daqinzl1 小时前
java获取机器ip、mac
java·mac·ip