首页 > 其他分享 >线程的生命周期和状态?

线程的生命周期和状态?

时间:2023-09-02 23:32:38浏览次数:43  
标签:状态 生命周期 Thread lock WAITING RUNNABLE 线程

Java 线程的状态及转换

线程状态的实质

当我们说线程的状态时,说的就是一个变量的值。哪个变量?Thread类中的一个变量

private volatile int threadStatus = 0;

这个值是个整数,可以通过映射关系(VM.toThreadState)转换成一个枚举类

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

我们盯着这个threadStatus的值看变化就好了、

NEW

一切的起点,要从把一个 Thread 类的对象创建出来,开始说起。

Thread t = new Thread();

alt

RUNNABLE

躺在堆内存中无所事事的 Thread 对象,在调用了 start() 方法后,才显现生机。

t.start();

alt

RUNNING 和 READY

上面的 RUNNABLE 状态,准确说是,得到了可以随时准备运行的机会的状态。

而处于这个状态中的线程,也分为了正在 CPU 中运行的线程,和一堆处于就绪中等待 CPU 分配时间片来运行的线程。

alt

处于就绪中的线程,会存储在一个就绪队列中,等待着被操作系统的调度机制选到,进入 CPU 中运行。

无论是 Java 语言,还是操作系统,都不区分这两种状态,在 Java 中统统叫 RUNNABLE。

TERMINATED

当一个线程执行完毕(或者调用已经不建议的 stop 方法),线程的状态就变为 TERMINATED。

alt

此时这个线程已经无法死灰复燃了,如果你此时再强行执行 start 方法,将会报出错误。java.lang.IllegalThreadStateException

最简单的线程生命周期讲完了。。。

初始 -- 运行 -- 终止

BLOCKED

首先创建一个对象 lock。

public static final Object lock = new Object();

一个线程,执行一个 sychronized 块,锁对象是 lock,且一直持有这把锁不放。

new Thread(() -> {
    synchronized (lock) {
        while(true) {}
    }
}).start();

另一个线程,也同样执行一个锁对象为 lock 的 sychronized 块。

new Thread(() -> {
    synchronized (lock) {
       ...
    }
}).start();

那么,在进入 synchronized 块时,因为无法拿到锁,会使线程状态变为 BLOCKED

同样,对于 synchronized 方法,也是如此。

当该线程获取到了锁后,便可以进入 synchronized 块,此时线程状态变为 RUNNABLE。

alt

WAITING

wait/notify

我们在刚刚的 synchronized 块中加点东西。

new Thread(() -> {
    synchronized (lock) {
       ...
       lock.wait();
       ...
    }
}).start();

这个lock.wait()方法一调用,会发生三件事。

  1. 释放锁对象lock(隐含着必须先获取这个锁才行)
  2. 线程状态变成WAITING
  3. 线程进入lock对象的等待队列

alt

什么时候这个线程被唤醒,从等待队列中移出,并从WAITING状态返回RUNNABLE状态呢?

必须由另一个线程,调用同一个对象的 notify/notifyAll 方法。

new Thread(() -> {
    synchronized (lock) {
       ...
       lock.notify(); 
       ...
    }
}).start();

只不过 notify 是只唤醒一个线程,而 notifyAll 是唤醒所有等待队列中的线程。

但需要注意,被唤醒后的线程,从等待队列移出,状态变为 RUNNABLE,但仍然需要抢锁,抢锁成功了,才可以从 wait 方法返回,继续执行。

如果失败了,就和上一部分的 BLOCKED 流程一样了。

alt

join

主线程这样写。

public static void main(String[] args) {
  thread t = new Thread(...);
  t.start();
  t.join();
  ...
}

当执行到 t.join() 的时候,主线程会变成 WAITING 状态,直到线程 t 执行完毕,主线程才会变回 RUNNABLE 状态,继续往下执行。

看起来,就像是主线程执行过程中,另一个线程插队加入(join),而且要等到其结束后主线程才继续。

因此我们的状态图,又多了两项。

alt

那 join 又是怎么神奇地实现这一切呢?也是像 wait 一样放到等待队列么?

打开 Thread.join() 的源码,你会发现它非常简单。

// Thread.java
// 无参的 join 有用的信息就这些,省略了额外分支
public synchronized void join() {
  while (isAlive()) {
      wait();
  }
}

也就是说,他的本质仍然是执行了 wait() 方法,而锁对象就是 Thread t 对象本身。

那从 RUNNABLE 到 WAITING,就和执行了 wait() 方法完全一样了。

那从 WAITING 回到 RUNNABLE 是怎么实现的呢?

主线程调用了 wait ,需要另一个线程 notify 才行,难道需要这个子线程 t 在结束之前,调用一下 t.notifyAll() 么?

答案是否定的,那就只有一种可能,线程 t 结束后,由 jvm 自动调用 t.notifyAll(),不用我们程序显示写出。

alt

park/unpark

有了上面 wait 和 notify 的机制,下面就好理解了。

一个线程调用如下方法。

LockSupport.park()

该线程状态会从 RUNNABLE 变成 WAITING、

另一个线程调用

LockSupport.unpark(Thread 刚刚的线程)

刚刚的线程会从 WAITING 回到 RUNNABLE。

但从线程状态流转来看,与 wait 和 notify 相同。

从实现机制上看,他们甚至更为简单。

  1. park 和 unpark 无需事先获取锁,或者说跟锁压根无关。

  2. 没有什么等待队列一说,unpark 会精准唤醒某一个确定的线程。

  3. park 和 unpark 没有顺序要求,可以先调用 unpark

关于第三点,就涉及到 park 的原理了,这里我只简单说明。

线程有一个计数器,初始值为0

调用 park 就是:如果这个值为0,就将线程挂起,状态改为 WAITING。如果这个值为1,则将这个值改为0,其余的什么都不做。

调用 unpark 就是:将这个值改为1

alt

TIMED_WAITING

这部分就再简单不过了,将上面导致线程变成 WAITING 状态的那些方法,都增加一个超时参数,就变成了将线程变成 TIMED_WAITING 状态的方法了,我们直接更新流程图。

alt

这些方法的唯一区别就是,从 TIMED_WAITING 返回 RUNNABLE,不但可以通过之前的方式,还可以通过到了超时时间,返回 RUNNABLE 状态。

就这样。

还有,大家看。

wait 需要先获取锁,再释放锁,然后等待被 notify。

join 就是 wait 的封装。

park 需要等待 unpark 来唤醒,或者提前被 unpark 发放了唤醒许可。

那有没有一个方法,仅仅让线程挂起,只能通过等待超时时间到了再被唤醒呢。

这个方法就是

Thread.sleep(long)

我们把它补充在图里,这一部分就全了。

alt

再把它加到全局图中。

alt

调用 jdk 的 Lock 接口中的 lock,如果获取不到锁,线程将挂起,此时线程的状态是什么呢?

有多少同学觉得应该和 synchronized 获取不到锁的效果一样,是变成 BLOCKED 状态?

不过如果你仔细看我上面的文章,有一句话提到了,jdk 中锁的实现,是基于 AQS 的,而 AQS 的底层,是用 parkunpark 来挂起和唤醒线程,所以应该是变为 WAITINGTIMED_WAITING 状态。

调用阻塞 IO 方法,线程变成什么状态?

比如 socket 编程时,调用如 accept(),read() 这种阻塞方法时,线程处于什么状态呢?

答案是处于 RUNNABLE 状态,但实际上这个线程是得不到运行权的,因为在操作系统层面处于阻塞态,需要等到 IO 就绪,才能变为就绪态。

Thread.yield( )

Java线程中有一个Thread.yield( )方法,很多人翻译成线程让步。

顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行。

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。

我把100块钱丢在地上,说:“我要yield!看看谁抢的块”

理论上丢在地上你们强的快,但也有可能是我抢到,或许你的手是最长的,但也不是一定就是你抢到。

标签:状态,生命周期,Thread,lock,WAITING,RUNNABLE,线程
From: https://blog.51cto.com/u_15872442/7335495

相关文章

  • 深入理解 Vuex:Vue 状态管理的核心
    在Vue.js应用程序中,管理组件之间的状态是一项关键任务。为了有效地管理和共享应用程序的数据,Vue.js开发团队开发了一个强大的状态管理库,称为Vuex。本文将深入探讨Vuex的核心概念、用法和最佳实践,帮助您更好地理解和利用这一工具。什么是Vuex?Vuex是一个专为Vue.js设计的状态管理库,......
  • 多线程学习第四篇
    4、线程同步机制并发:同一对象被多个线程同时操作(抢票)线程同步是一个等待机制,多个需要同时访问次对象的线程进入这个对象的等待池形成队列,等待前一个线程使用完毕,下一个线程才能使用。形成线程安全的条件:队列和锁由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也......
  • 多线程学习第五篇
    5、线程协作(线程通信)应用场景:生产者和消费者问题假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。如果仓库中没有产品,则将生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以......
  • 多线程学习第三篇
    3、线程状态线程五大状态:创建状态:通过new创建线程就绪状态:通过start()启动线程进入就绪状态阻塞状态:通过CPU调配进入运行状态运行状态:在运行状态时,可以进行如sleep,wait等方法使线程进入阻塞状态死亡状态:自然执行完毕、外部干涉终止线程具体流程为:3.1、线程的常用......
  • 多线程第一篇(认识多线程)
    多线程任务,进程,线程,多线程Process:进程Thread:线程1、基本概念进程在操作系统中运行的程序就是进程。程序是指令和数据的有序集合,其本身没有任何运行的含义,是静态的。进程就是执行程序的一次执行过程,它是一个动态的概念式系统资源分配的单位通常再一个进程中可......
  • 每周总结-第八周 多线程
    多线程概述:充分利用计算机资源,同时执行不同的操作1.计算机操作系统进程和线程2.使用java来完成多线程的编码3.线程中的常用方法4.线程同步(重点)5.死锁6.生产者消费者模型异步操作系统简介操作系统:本质上就是一个运行在一堆硬件上的巨型软件没有操作系统的话,程序想要操控......
  • MYSQL数据库备份还原,并还原到最新状态(mysqldump)
    启用二进制日志文件vim/etc/my.cnf配置文件位置及文件名根据实际情况确定<br>sql_log_bin=on|off:是否记录二进制日志,默认为on在需要的时候设置为off=""<br>log_bin="/PATH/BIN_LOG_FILE:指定二进制日志文件位置;"通常单独存放到与数据库不同的机器中=""<=""p=""></br>......
  • 多线程|饿汉模式和懒汉模式
    单例模式是只有单个实例的模式,应用在只能有一个实例的场景中。单例模式有很多种,这里介绍饿汉模式和懒汉模式两个单例。一、饿汉模式“饿汉”是一种形象的描述,“饿汉”看到吃的就非常急切,把这种急切的形象类比到Java中就是在类加载阶段就把实例创建出来了。什么是类加载?Java代码......
  • Spring Bean 的生命周期,如何被管理的
    实例化一个Bean,也就是我们通常说的new按照Spring上下文对实例化的Bean进行配置,也就是IOC注入如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(StringbeanId)方法,此处传递的是Spring配置文件中Bean的ID如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBean......
  • HashMap线程安全(含答案)
    HashMap线程安全(含答案)Java中平时用的最多的Map集合就是HashMap了,它是线程不安全的。看下面两个场景:1、当用在方法内的局部变量时,局部变量属于当前线程级别的变量,其他线程访问不了,所以这时也不存在线程安全不安全的问题了。2、当用在单例对象成员变量的时候呢?这时候多个线程过来访......