java线程的五大状态,阻塞状态详解

2023-11-01

一、状态简介

一个线程的生命周期里有五大状态,分别是:

  1. 新生
  2. 就绪
  3. 运行
  4. 死亡
  5. 运行后可能遇到的阻塞状态

在这里插入图片描述

二、相关方法

2.1 新生状态
Thread t = new Thread();

正如我们前面所说的,一个线程开始之后有自己的内存空间,这些工作空间和主内存进行交互,从主内存拷贝数据到工作空间。

当这个语句执行的时候,线程创建,开辟工作空间,也就是线程进入了新生状态。

2.2 就绪状态

普通情况,一旦调用了:

t.start();

start 方法被调用,线程立即进入了就绪状态,表示这个线程具有了运行的条件,但是还没有开始运行,这就是就绪状态。

线程就绪,但不意味着立即调度执行,因为要等待CPU的调度,一般来说是进入了就绪队列

但是还有另外三种情况,线程也会进入就绪状态,四种分别是:

  1. start()方法调用;
  2. 本来处于阻塞状态,后来阻塞解除;
  3. 如果运行的时候调用 yield() 方法,避免一个线程占用资源过多,中断一下,会让线程重新进入就绪状态。注意,如果调用 yield() 方法之后,没有其他等待执行的线程,此线程就会马上恢复执行;
  4. JVM 本身将本地线程切换到了其他线程,那么这个线程就进入就绪状态。
2.3 运行状态

当CPU选定了一个就绪状态的线程,进行执行,这时候线程就进入了运行状态,线程真正开始执行线程体的具体代码块,基本是 run() 方法。

注意,一定是从 就绪状态 - > 运行状态,不会从阻塞到运行状态的。

2.4 阻塞状态

阻塞状态指的是代码不继续执行,而在等待,阻塞解除后,重新进入就绪状态。

也就是说,阻塞状态发生肯能是运行状态转过去的, 运行状态 - > 阻塞状态,不会从就绪状态转过去。

阻塞的方法有四种:

  1. sleep()方法,是占用资源在睡觉的,可以限制等待多久;
  2. wait() 方法,和 sleep() 的不同之处在于,是不占用资源的,限制等待多久;
  3. join() 方法,加入、合并或者是插队,这个方法阻塞线程到另一个线程完成以后再继续执行;
  4. 有些 IO 阻塞,比如 write() 或者 read() ,因为IO方法是通过操作系统调用的。

上面的方法和start() 一样,不是说调用了就立即阻塞了,而是看CPU。

2.5 死亡状态

死亡状态指的是,线程体的代码执行完毕或者中断执行。一旦进入死亡状态,不能再调用 start() 。

让线程进入死亡状态的方法是 stop() 和 destroy() 但是都不推荐使用,jdk里也写了已过时。

一般的做法是,在线程内,让线程自身自然死亡,或者加一些代码,想办法让线程执行完毕。

  1. 自然死亡:这个线程体里就是多少次的循环,几次调用,执行完了就完了。
  2. 如果不能自然死亡:加一些终止变量,然后用它作为run的条件,这样,外部调用的时候根据时机,把变量设置为false。

比如下面的写法,第一种就是我们的正常写法(虽然很简单但是没有用lambda表达式,主要为了和第二种对比)

/*
    终止线程的方法1:自然死亡
*/
public class Status implements Runnable{
    @Override
    public void run() {
        for (int i=0; i<20; i++){
            System.out.println("studying");
        }
    }

    public static void main(String[] args) {
        Status s = new Status();
        new Thread(s).start();
    }
}
/*
    终止线程的方法2:外部控制
*/
public class Status implements Runnable{
    //1.加入状态变量
    private boolean flag = true;
    @Override
    //2.关联状态变量
    public void run() {
        while (flag){
            System.out.println("studying");
        }
    }
    //3.对外提供状态变量改变方法
    public void terminate() {
        this.flag = false;
    }
    public static void main(String[] args) {
        Status s = new Status();//1.新生
        new Thread(s).start();//2.就绪,随后进入运行
        //无人为阻塞
        for (int i=0; i<100000; i++){
            if (i==80000){
                s.terminate();//3.终止
                System.out.println("结束");
            }
        }
    }
}

三、阻塞状态详解

上面的内容,线程的五大状态里,其他四种都比较简单,创建对象的新生态、start开始的就绪态、cpu调度之后进入的运行态,以及正常结束或者外加干预导致的死亡态。

在这里插入图片描述

3.1 sleep()
  • sleep(时间)指定当前线程阻塞的毫秒数:
  • sleep存在异常:InterruptException;
  • sleep时间到了之后线程进入就绪状态;
  • sleep可以模拟网络延时、倒计时等;
  • 每一个对象都有一个无形的锁,sleep不会释放锁。(也就是我们说过的,抱着资源睡觉,这个特点对比wait)

前面用线程模拟抢票的网络延时,已经做过示例,就是用sleep设置线程阻塞的时间,达到网络延时的效果,让那个同步问题更容易显现出来。

我们再用龟兔赛跑的例子修改一下,前面兔子和乌龟都是一样各自跑,这次让兔子骄傲的爱睡觉,每隔一段路就睡一段时间。

public class Racer2 implements Runnable{
    private String winner;
    @Override
    public void run() {
        for (int dis=1; dis<=100; dis++){
            String role = Thread.currentThread().getName();
            //模拟兔子睡觉
            if (dis%10==0 && role.equals("兔子")){
                try {
                    Thread.sleep(500);//睡
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(role + " 跑了 " + dis);
            //每走一步,判断是否比赛结束
            if (gameOver(dis))break;
        }
    }

    public boolean gameOver(int dis){
        if (winner != null){
            return true;
        } else if (dis == 100){
            winner = Thread.currentThread().getName();
            System.out.println("获胜者是 "+winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Racer2 racer = new Racer2();//1.创建实现类
        new Thread(racer,"兔子").start();//2.创建代理类并start
        new Thread(racer,"乌龟").start();
    }
}

这里面:

Thread.sleep(500);//睡

就是让线程进入阻塞状态,并且 500 ms 后,自动进入就绪状态,由 cpu 调度适时重新运行。

也可以利用 sleep 模拟倒计时,不涉及多个线程,所以直接在主线程里面,主方法里写内容就可以,也不用run什么,直接调用sleep设置阻塞时间。

public class SleepTime {
    public static void main(String[] args) throws InterruptedException {
        int num = 10;
        while (num>=0){
            Thread.sleep(1000);
            System.out.println(num--);
        }
    }
}

这就会完成10-0 的倒计时。

更花哨一点,用 Dateformat 和 Date 实现时间的显示:

public static void main(String[] args) throws InterruptedException {
    //获取十秒后的时间,然后倒计时
    Date endTime = new Date(System.currentTimeMillis()+1000*10);
    //干预线程的结束
    long end = endTime.getTime();
    DateFormat format = new SimpleDateFormat("mm:ss");
    while (true) {
        System.out.println(format.format(endTime));
        Thread.sleep(1000);
        endTime = new Date(endTime.getTime() - 1000);//-1s
        if (end-10000 > endTime.getTime()){
            break;
        }
    }
}
3.2 wait() && notify()

和 sleep() 不同,wait() 方法和 notify() 搭配,可以互相搭配,一个经典的的使用场景以及介绍是在生产者-消费者模型中:

生产者消费者模型详解

3.3 yield()

礼让线程,让当前正在执行的线程暂停,不阻塞线程,而是直接将线程从运行状态转入就绪状态,让cpu调度器重新调度。

用法和 sleep 是一样的,也是一个静态方法,调用起来的效果比 sleep 弱。

这是因为,礼让之余,还有 cpu 的调度在影响顺序,所以无法保证达到线程切换的效果, cpu 还是可能调用当前的线程。

3.4 join()

join 合并线程,待此线程执行完成后,再执行其他线程。也就是说,阻塞其他的所有线程,所以其实应该是插队线程,所以 join 应该翻译成加入,插入?。

不同于 sleep 和 yield 方法,join 不是静态方法,是一个普通方法,要通过一个具体的 Thread 对象才能调用 join。

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i=0; i<100; i++){
                System.out.println("myThread is joining" + i);
            }
        });

        t.start();

        for (int i=0; i<100; i++){
            if (i == 50){
                t.join();//插队,此时主线程不能执行,而要执行 t
            }
            System.out.println("main is running" + i);
        }
    }
}

执行结果可以看出来,等到主线程执行到 i = 50后,t 线程完全执行,直到结束,才继续执行 main 线程。

(当然,在 i = 50 之前,是不确定的,cpu 给两个线程的安排)

join 的最重要的部分,就是插队可以保证,这个线程自己一定会先执行完,这是在很多地方需要的逻辑。

四、利用枚举变量监控线程状态

回过头看线程的状态转换图:

在这里插入图片描述
新生态、死亡态、除了阻塞内部,其他都已经进行了练习,其中,就绪态到运行态之间是不由程序员控制的,所以 java 给这两个状态了一个统一的名称叫 Runnable(不要和Runnable接口搞混)。

java jdk 里面对于线程状态的区分:

在这里插入图片描述

  1. NEW 对应没有 Started 的线程,对应新生态;
  2. RUNNABLE,对于就绪态和运行态的合称;
  3. BLOCKED,WAITING,TIMED_WAITING三个都是阻塞态:
    • sleep 和 join 称为WAITING,TIMED_WAITING(设置了时间的话);
    • wait 和 IO 流阻塞称为BLOCKED。
  4. TERMINATED 死亡态。

我们用一个 demo 观察一下状态的切换过程:

public class AllState {
    public static void main(String[] args) throws InterruptedException {
        //一个拥有阻塞的线程
        Thread t = new Thread(()->{
            for (int i=0; i<10; i++){
                try {
                    Thread.sleep(200);//阻塞
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("........I am running.........");
            }
        });

        System.out.println(t.getState());//获取状态,此时应该是new

        t.start();
        System.out.println(t.getState());//获取状态,此时已经start

        //监控阻塞,设置结束条件为监控到 t 线程已经变成 Terminated
        while (!t.getState().equals(Thread.State.TERMINATED)){
            Thread.sleep(200);//主线程每200ms监控一次线程 t
            System.out.println(t.getState());
        }
    }
}

在这里插入图片描述
从输出结果可以看到,从 开始的 new 到 runnable(start后),到有线程执行了 running 的runnable,到阻塞的 timed_waiting ,到恢复 runnable,到最终的结束 terminated。

Thread 还提供了线程数方法,可以计数,结束条件其实还可以改成对于线程数的判断,因为当 t 结束后,线程数就只剩下主线程了

while (Thread.activeCount() != 1){
    System.out.println(Thread.activeCount());
    Thread.sleep(200);//主线程每200ms监控一次线程 t
    System.out.println(t.getState());
}

然而,运行起来的时候输出显示的是 3 个线程:

在这里插入图片描述
最后 terminated 之后陷入了线程数是 2 的死循环,和预想的不一样。。。

引入了另一个问题,搜了一下,应该是控制台输出,也是一个线程被监控的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java线程的五大状态,阻塞状态详解 的相关文章

  • java多线程:线程池和阻塞队列

    一 线程池定义和使用 jdk 1 5 之后就引入了线程池 1 1 定义 从上面的空间切换看得出来 线程是稀缺资源 它的创建与销毁是一个相对偏重且耗资源的操作 而Java线程依赖于内核线程 创建线程需要进行操作系统状态切换 为避免资源过度消耗
  • JUC编程

    1 JUC JUC就是java util concurrent工具包的简称 这是一个处理线程的工具包 JDK 1 5开始出现的 1 传统的synchronized public class Synchronized public stati
  • java实现一个整数和一个小数的四则运算和求最大值,平均值。

    实现一个整数和一个小数的四则运算和求最大值 平均值 import java applet Applet import java awt public class Yunsuan extends Applet Label prompt1 pr
  • Kafka 顺序消费方案

    Kafka 顺序消费方案 前言 1 问题引入 2 解决思路 3 实现方案 前言 本文针对解决Kafka不同Topic之间存在一定的数据关联时的顺序消费问题 如存在Topic insert和Topic update分别是对数据的插入和更新 当
  • .Net/C#: 实现支持断点续传多线程下载的 Http Web 客户端工具类 (C# DIY HttpWebClient)

    选择自 playyuer 的 Blog Net C 实现支持断点续传多线程下载的 Http Web 客户端工具类 C DIY HttpWebClient Reflector 了一下 System Net WebClient 重载或增加了若干
  • Java封装性(包含this关键字,构造器等)

    目录 一 封装性的含义 二 封装性的作用 三 封装性的体现 3 1 四种权限修饰符的介绍 3 2 分装性具体的实现 四 构造器的解释 4 1 构造器的作用 4 2 注意事项 4 3 构造器的举例说明 五 this关键字的使用 5 1 thi
  • IDEA卡顿怎么办?快来用用这个办法

    IDEA卡顿解决方法 亲测有效 1 找到IDEA安装位置 打开这两个配置 2 修改配置 3 保存配置 重启IDEA 先介绍一下我电脑的情况 华硕dx80 8g运行 电脑配置一般 在跟同等价位的拯救者同时打开IDEA时 打开速度都差好多 为了
  • 线程封闭概念

    线程封闭概念 为什么要有线程封闭这个概念呢 多线程中访问共享可变数据时 涉及到线程间数据同步的问题 并不是所有时候都需要共享数据 所以线程封闭概念就出来了 在Java中线程封闭该怎么做呢 可以通过两个方法来做 ThreadLocal 1 T
  • 条件变量(condition variable)详解

    原理 假设我们需要解决这样一个问题 一个列表记录需要处理的任务 一个线程往此列表添加任务 一个线程processTask处理此列表中的任务 这个问题的一个关键点在于processTask怎么判断任务列表不为空 一般有两种方法 一 proce
  • Cuda Streams的概述(四)-- 同步

    同步 同步的APIs 同步所有的事情 阻塞host端 直到所有的CUDA调用完成 cudaDeviceSynchronize 同步主机端特定的流 阻塞host端 直到流里的CUDA调用完成 cudaStreamSynchronize str
  • java中什么是并发,如何解决?

    多个进程或线程同时 或着说在同一段时间内 访问同一资源会产生并发问题 银行两操作员同时操作同一账户就是典型的例子 比如A B操作员同时读取一余额为1000元的账户 A操作员为该账户增加100元 B操作员同时为该账户减去 50元 A先提交 B
  • 多线程快速处理List集合(结合线程池的使用)

    有一个大List集合 遍历进行一些耗时操作 不能达到性能要求 查询日志 单个任务虽然有不少数据库和第三方API请求 比较耗时 但返回效率尚可 所以优先采用多线程方式进行处理并行请求数据库和第三方API 因为处理完还要对list所属的数据进行
  • 主线程退出后,子线程会不会退出

    额 好吧 这是个标题党 其实所有的线程都是平级的 根本不存在主线程和子线程 下文所述为了方便 将在main函数中的线程看做主线程 其它线程看成子线程 特此说明 先考虑以下代码 include
  • [Java实现 Scoket实时接收Tcp消息 优化层层叠加]

    目录 前言 基础实现代码 描述 优化代码多线程处理客户端连接和消息接收 描述 再次优化异步实现 以下是使用 CompletableFuture 实现异步处理客户端请求的示例代码 描述 进一步优化的代码 Netty来实现Socket服务器 描
  • MFC多线程编程之一——问题提出

    原文地址 http www vckbase com document viewdoc id 1704 一 问题的提出 编写一个耗时的单线程程序 新建一个基于对话框的应用程序SingleThread 在主对话框IDD SINGLETHREAD
  • JAVA使用线程池查询大批量数据

    前言 在开发过程中可能会碰到某些独特的业务 比如查询全部表数据 数据量过多会导致查询变得十分缓慢 虽然在大多数情况下并不需要查询所有的数据 而是通过分页或缓存的形式去减少或者避免这个问题 但是仍然存在需要这样的场景 比如需要导出所有的数据到
  • 复制虚拟机之后网关重启问题解决

    在复制完成之后没有可以连接的IP地址 于是百度寻求解决方案 根据找到的方案中 实际解决办法如下 1 输入以下命令 清空该文件内容 echo gt etc udev rules d 70 persistent rules 2 删除该文件 或者
  • 线程池用例

    线程池逻辑类 public class TaskExecutorService private final ExecutorService pool private final ThreadPoolExecutor pool private
  • Java多线程 - 线程池常用容量设置

    线程执行方式 线程的执行是由CPU进行调度的 一个CPU在 同一时刻只会执行一个线程 操作系统利用了时间片轮转的方式 CPU给每个任务都服务一定的时间 然后把当前任务的状态保存下来 再加载下一个任务的状态后 继续服务下一个任务 任务的保存及
  • Java多线程(7):并发_线程同步_队列与锁(Synchronized)

    一 并发举例 线程不安全 1 两个人同时操作一张银行卡 如何保证线程安全 2 多个人同时购买一张火车票 谁能买到 二 并发特点 1 同一个对象 2 被多个线程操作 3 同时操作 三 如何保证线程安全 线程同步 队列 锁 1 使用队列的技术一

随机推荐