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;
        }
    }
}

命令行演示:

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

相关推荐
陈大爷(有低保)16 分钟前
UDP Socket聊天室(Java)
java·网络协议·udp
kinlon.liu29 分钟前
零信任安全架构--持续验证
java·安全·安全架构·mfa·持续验证
王哲晓1 小时前
Linux通过yum安装Docker
java·linux·docker
java6666688881 小时前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存1 小时前
源码分析:LinkedList
java·开发语言
执键行天涯1 小时前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
Jarlen1 小时前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar
蓑 羽1 小时前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode
Reese_Cool1 小时前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言