全面详解Java多线程基础-2:线程的状态

news2024/12/27 18:38:28

相关阅读:

全面详解Java多线程基础-1:Thread类及其基本用法

操作系统里的进程,自身是有一个状态的。而Java的Thread类是对系统线程的封装,它把这里的“状态”又进一步精细化了。

理解线程状态,意义是能够让我们更好地进行多线程代码的调试。例如,当发现两个线程都处在 BLOCKED 状态,就可考虑是否发生的死锁。


目录

一、观察线程中的所有状态

1、NEW

2、TERMINATED

3、RUNNABLE

4、TIMED_WAITING

二、线程状态的转移

1、示意图概述

2、关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换

3、关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换

4、yield()让出CPU


 

一、观察线程中的所有状态

t.getState() 方法查看调用时线程的状态。

线程的状态包括:

  • NEW:系统中的线程还没创建出来,只是有了一个 Thread 对象( 已经安排好了工作,但是 还未开始行动)。
  • TERMINATED:系统中的线程已经执行完了,但 Thread 对象还在(工作已经完成了)。
  • RUNNABLE:就绪状态。有两种可能的情况:①线程正在CPU上运行;②线程已经准备好随时可以去CPU上运行(表示“可工作的”状态;又可以分成正在工作中和即将开始工作)。
  • TIMED_WAITING:线程正在“指定时间等待”,表示线程在有限时地等待唤醒(等待其它线程发来通知)。一个典型的状况就是线程正在休眠sleep()。
  • BLOCKED:表示等待获取锁的状态。
  • WAITING:使用 wait() 方法出现,表示线程在无限时等待唤醒(等待其它线程发来通知)。

TIMED_WAITING、BLOCKED、WAITING都表示排队等着其他某件事情。

示意图

 

1、NEW

NEW 表示系统中的线程还没创建出来,只是有了一个 Thread 对象。

public class ThreadDemo {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("good!");
        });

        // 在线程启动之前,获取线程状态 getState()  NEW
        System.out.println(t.getState());
        t.start();
    }
}

2、TERMINATED

TERMINATED 表示系统中的线程已经执行完了,但 Thread 对象还在。

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("good!");
        });

        // 在线程启动之前,获取线程状态 getState()  NEW
        System.out.println(t.getState());
        t.start();

        // 线程已经执行完了,但Thread对象还在  TERMINATED
        // 为了保证调用t.getState()的时候,线程已经执行完了,先调用sleep让main线程休眠等待
        Thread.sleep(5000);
        System.out.println(t.getState());
    }
}

3、RUNNABLE

表示线程处于就绪状态。

它有两种可能的情况:①线程正在CPU上运行;②准备好随时可以去CPU上运行。

概括来说,就是一个“随叫随到”的状态。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(true) {
                //System.out.println("hello!");
            }
        });

        // 在线程启动之前获取线程状态:NEW
        System.out.println(t.getState());

        t.start();

        Thread.sleep(5000);
        // 此时调用t.getState()获取线程状态,线程仍正在执行,,因此是RUNNABLE
        System.out.println(t.getState());
    }
}

4、TIMED_WAITING

表示线程正在“指定时间等待”。一个典型的状况就是线程正在休眠sleep()。

如下面代码中,在 t 线程内,做的事情就是 sleep() 休眠。这样,最后一行代码查询线程状态时,由于 t 线程正在休眠,即正在“指定时间等待”,因此就会显示状态为TIMED_WAITING。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        // 在线程启动之前获取线程状态:NEW
        System.out.println(t.getState());

        t.start();

        Thread.sleep(5000);
        System.out.println(t.getState());
    }
}


二、线程状态的转移

1、示意图概述

*一条主线,三条支线。

详细补充:

(图片来自:https://blog.csdn.net/pange1991)

2、关注 NEW RUNNABLE TERMINATED 状态的转换

通过以下代码可以观察到线程在NEW、RUNNABLE和TERMINATED三个状态之间的转换。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 1000_0000; i++) {
                //循环体是空的
            }
        }, "线程t");

        // ①在线程启动之前
        System.out.println(t.getName() + ": " + t.getState());

        t.start();

        while (t.isAlive()) {
            // ②在线程存活期间
            System.out.println(t.getName() + ": " + t.getState());
        }

        // ③在线程终止后
        System.out.println(t.getName() + ": " + t.getState());;
    }
}

首先,我们创建一个线程t。t 的线程体中设置一个循环,该循环将执行 10000000 次,循环执行完毕后线程便终止了。

然后,我们分别在三个位置查看线程t的状态:

①在线程启动之前。此时线程的状态是NEW。

②在线程的存活期间内。isAlive 方法用于判定线程的存活状态,存活指的是线程已经启动且尚未终止,当线程正在运行或着准备开始运行的时候,该方法就认为线程是“存活”的,并以true或false的形式返回。此时调用线程可以得到 RUNNABLE 的状态。

③在线程终止后。这时,线程的状态是TERMINATED。

3、关注 WAITING BLOCKED TIMED_WAITING 状态的转换

通过以下代码和jconsole工具可以观察到线程在WAITING、BLOCKED和TIMED_WAITING 三个状态之间的转换。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        final Object object = new Object();

        // 创建线程t1
        Thread t1 = new Thread(() -> {
            synchronized (object) {
                while (true) {
                    try {
                        Thread.sleep(1000);
                   } catch (InterruptedException e) {
                        e.printStackTrace();
                   }
               }
            }
       }, "t1");

        // 启动t1
        t1.start();

        // 创建线程t2
        Thread t2 = new Thread(() -> {
            synchronized (object) {
                System.out.println("hehe");
           }
       }, "t2");

        //启动t2
        t2.start();
    }
}

创建 t1 线程,并给该线程加锁;线程体内t1线程不断进行休眠sleep()操作。

创建 t2 线程,也给 t2 线程加锁;在t2线程中打印hehe。

启动进程并打开jconsole,可以观察到,t1 的状态为 TIME_WAITING,t2 的状态为 BLOCKED。

这是因为启动t1时,t1获得了锁,并进入了sleep循环。此时的t1一直在进行“指定时间等待”的操作,因此状态为 TIME_WAITING。启动t2,此时t1还未释放锁,t2只能等待获取锁。t1一直不释放锁,t2就只能一直等待。因此,t2的状态是BLOCKED。

此时控制台的状况为一片空白,即什么也没有打印。

如果此时,我们把t1中的sleep改成wait,t1的状态就变为了WAITING。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        final Object object = new Object();

        // 创建线程t1
        Thread t1 = new Thread(() -> {
            synchronized (object) {
                try {
                    object.wait();
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
            }
       }, "t1");

        // 启动t1
        t1.start();

        // 创建线程t2
        Thread t2 = new Thread(() -> {
            synchronized (object) {
                System.out.println("hehe");
           }
       }, "t2");

        //启动t2
        t2.start();
    }
}

这里必须特别注意t1线程中调用的 object.wait() 方法。

在Java中,线程必须持有对象的锁才能调用 wait() 方法。当线程调用 wait() 方法时,它会释放当前持有的锁,并将自己挂起,直到被其他线程唤醒。一旦线程调用了 wait() 方法并释放了锁,其他线程就可以获取该对象的锁并继续执行同步块中的代码。当该线程被唤醒(notify)时,它会重新尝试获取对象的锁并恢复执行。但是,在其它线程未释放锁之前该线程仍然会被阻塞。一旦它获得了对象的锁,它才可以继续执行同步块中的代码。

因此,在上面的代码中,当 t1 调用 object.wait() 方法时,它会释放 object 对象的锁。这意味着,在 t1 等待时,其他线程(如 t2)可以获得该对象的锁并执行同步块中的代码,打印出hehe。但是,只有当 t1 线程被唤醒并重新获得对象的锁时,它才会继续执行同步块中的代码。t1一直不被唤醒,就一直处于无限等待的状态。此时查看t1的线程状态,可见为WAITING。

这里我们可以得出结论:

  • BLOCKED 表示等待获取锁,WAITING TIMED_WAITING 表示等待其他线程发来通知。
  • TIMED_WAITING 线程在等待唤醒,但是设置了时限的;WAITING 线程在无限地等待唤醒。

4、yield()让出CPU

yield 不会改变线程的状态,但会让线程重新去排队。

yield() 方法是Java中的一个静态方法,它属于 Thread 类,用于使当前线程让出CPU资源,让其他具有相同或更高优先级的线程运行。调用 yield() 方法并不会释放锁或者阻塞当前线程,它只是暂停当前线程的执行,并允许其他线程获得执行。当当前线程被重新调度时,它将继续执行,但并不能保证它会立即被调度。

但是,调用 yield() 方法并不能保证其他线程会获得执行,因为调度器可以选择忽略 yield() 方法的调用。过度使用 yield() 方法可能会导致性能问题,因为频繁的调用 yield() 方法可能会导致线程频繁地切换,从而降低系统的性能。

通常情况下,使用 yield() 方法是为了避免长时间的占用CPU资源。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                      System.out.println("张三");
                      // 先注释掉, 再放开
                      // Thread.yield();
              }
            }
        }, "t1");
        
        t1.start();
        
        Thread t2 = new Thread(new Runnable() {
          @Override
            public void run() {
                while (true) {
                      System.out.println("李四");
                }
            }
        }, "t2");
        
        t2.start();
    }
}
可以观察到,在不使用 yield 时,打印的“ 张三”和“李四”数量大概各占一半;但使用 yield 后 , “ 张三”的数量远远少于“李四”。这是因为每次执行到t1线程中的 Thread.yield() 方法时,都会使t1线程暂时让出CPU资源,让t2线程获得执行。t2线程可以被执行到的次数就大大增加了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/439846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

当ChatGPT遇上StableDiffusion ChatGPT指导StableDiffusion绘画

ChatGPT指导StableDiffusion 前言开始使用场景1&#xff1a;繁华的街道场景2&#xff1a;桥上的女生 项目及教程地址&#xff0c;附GPT访问地址安装教程及安装包地址&#xff1a;点我下载开源项目&#xff1a;点我下载GPT访问地址&#xff0c;点我访问 前言 Stable Diffusion …

java下

双列集合的特点 双列集合一次需要存一对数据&#xff0c;分别为键和值键不能重复&#xff0c;值可以重复键和值是一一对应的&#xff0c;每一个键只能找到自己对应的值键&#xff0b;值这个整体我们称之为“键值对”或者“键值对对象”&#xff0c;在Java中叫做“Entry对象” …

git及vs2019代码量统计的方法

git 在工程下打开git bash //替换username&#xff0c;查看个人代码量 git log --author"username" --prettytformat: --numstat | awk { add $1; subs $2; loc $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", ad…

基于matlab之NR SSB 光束扫描仿真

一、前言 此示例说明如何在 5G NR 系统的发射器 &#xff08;gNB&#xff09; 和接收器 &#xff08;UE&#xff09; 端使用波束扫描。本示例使用同步信号块 &#xff08;SSB&#xff09; 说明了初始访问期间使用的一些波束管理程序。为了完成声束扫描&#xff0c;该示例使用了…

人工智能全球发展趋势、经济影响和未来挑战

人工智能&#xff08;AI&#xff09;作为一种新兴技术&#xff0c;正在引领世界经济和社会的变革。在过去几年中&#xff0c;全球范围内对人工智能的投资和研究持续增长&#xff0c;这使得人工智能成为了各行各业的关注焦点。本文将介绍人工智能的基础知识和应用场景&#xff0…

3个问题!验明GPT-4真身;基于GPT科研加速技巧汇总;Midjourney神仙教程;印象笔记有AI功能啦 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『GPT-4 验明真身的三个经典问题』快速区分 GPT-3.5 与 GPT-4&#xff0c;快来对答案 这里收集了几个免费使用 GPT-4 的工具&#xff…

LVDS为汽车应用提供可靠的视频接口

摘要&#xff1a;汽车视频应用中&#xff0c;降低视频干扰的途径是用数字信号替代模拟信号。最有效的视频传输方案是采用低电压差分信号(LVDS)接口&#xff0c;因为它具有较低的信号幅度(0.35V)和差分架构&#xff0c;可大大降低电磁辐射。 新型汽车电子信号格式变化最快的是视…

如何在 iPhone 上恢复永久删除的照片?

iPhone永久删除的照片是否永远消失了&#xff1f; 你想知道永久删除的照片在 iPhone 上放在哪里吗&#xff1f;通常情况&#xff0c;操作系统会将已删除照片的存储空间进行标记为准备好覆盖&#xff08;还未实际删除&#xff09;。iPhone 也是如此。 如果互联网连接良好&a…

从功能到外企测开,工作1年半拿下年薪30万的测开 offer,未来可期

说一下我的大致情况&#xff0c;女&#xff0c;2018年毕业于末流211计算机本科。后来待业两年&#xff0c;完全没有从事互联网方面的工作。去年来到北京&#xff0c;在小公司做了一年多功能测试。今年11月底跳槽到外企&#xff0c;开始了我钱多事少离家近&#xff0c;每周965的…

如何用ChatGPT做品牌项目的二手信息搜集?

该场景对应的关键词库&#xff08;25个&#xff09;&#xff1a; 品牌案例、竞品、信息来源、项目分析、官方渠道、品类、品牌、节日节庆、明星、国家、奖项、代理商、项目名称、项目描述、品牌介绍、竞争情况、运营数据、财务信息、交易信息、法律问题、网络平台、行业人士、品…

线程池?我不懂?

为什么要使用线程池&#xff1f; 降低资源消耗。通过重复利用已创建的线程&#xff0c;降低线程创建和销毁造成的消耗提高响应速度。当任务到达时&#xff0c;任务可以不需要等到线程创建就能立即执行增加线程的可管理型。线程是稀缺资源&#xff0c;使用线程池可以进行统一分…

c++学习之c++对c的扩展1

目录 1.面向过程与面向对象的编程 2.面向对象编程的三大特点 3.c对c的扩展&#xff1a; 1.作用域运算符&#xff1a;&#xff1a; 2.命名空间 1.c命名空间&#xff08;namespace&#xff09; 2.命名空间的使用 1.在不同命名空间内可以创建相同的名称 2.命名空间只能在全…

Java基础——异常+IO流资源释放

异常 &#xff08;1&#xff09;什么是异常&#xff1a; 程序在编译/执行的过程中可能出现的问题注意&#xff1a;语法错误不属于异常 &#xff08;2&#xff09;为什么要学习异常&#xff1a; 异常一旦出现了&#xff0c;如果没有提前处理&#xff0c;程序就会退出JVM虚拟…

java的泛型

1. 泛型是什么 ​ Java泛型是J2 SE1.5中引入的一个新特性&#xff0c;其本质是参数化类型&#xff0c;也就是说所操作的数据类型被指定为一个参数&#xff08;type parameter&#xff09;, 这种参数类型可以用在类、接口和方法的创建中&#xff0c;分别称为泛型类、泛型接口、…

如何开展兼容性测试?兼容性测试有什么作用?

兼容性测试是指测试软件在特定的硬件平台上、不同的应用软件之间、不同的操作系统平台上、不同的网络等环境中是否能够很友好的运行的测试。兼容性测试是软件测试过程中必不可少的一个过程&#xff0c;没有兼容性测试的测试是不完整的测试&#xff0c;下面来分享怎么做兼容测试…

【艾特淘】淘宝卖家如何获取手淘推荐流量?

目前手淘推荐在流量构成中的占比越来越大。平台的推送机制越来越精准&#xff0c;卖家的使用习惯在慢慢改变&#xff0c;因此手淘推荐成了必争之地&#xff0c;尤其是免费的手淘推荐流量&#xff0c;成本很低&#xff01;那么今天就给大家介绍一下手淘推荐的基本操作思路。首先…

【MySQL | 基础篇】04、MySQL 多表查询

目录 一、多表关系 1.1 一对多 ​1.2 多对多 1.3 一对一 二、多表查询概述 2.1 数据准备 2.2 概述 2.3 分类 三、内连接 3.1 案例 四、外连接 4.1 案例 五、自连接 5.1 自连接查询 5.2 联合查询 六、子查询 6.1 概述 6.2 标量子查询 6.3 列子查询 6.4 行…

NIFI从MySql中离线读取数据再导入到MySql中_不带分页处理_01_QueryDatabaseTable获取数据---大数据之Nifi工作笔记0036

首先拖入一个处理器组,然后把名字修改一下 首先拖入一个处理器组,把名字修改成mysql数据实时采集 然后拖入一个querydatabasetable处理器,然后配置处理器的数据库连接池 创建一个数据库连接池 选择上图中的连接池,然后给service Name改个名字 然后我们点击右边的箭头,然后

用智能手机拍的模糊照片怎么办?学会这个技巧让它变得清晰

智能手机的相机功能越来越强大&#xff0c;但有时候我们还是会拍出一些模糊的照片。这可能是因为手抖或者光线不足等原因导致的。但不要担心&#xff0c;有一些简单的技巧可以帮助您将模糊的照片变得更加清晰。 1.稳定手机 拍摄清晰照片的第一步是确保相机保持稳定。拍照时最…

小技巧 - 王者荣耀战力查询修改战区技巧(轻松拿省标)

项目场景 这个小技巧可能部分资深王者荣耀玩家早就会了哦 主要就是先去查询一下王者荣耀英雄最低战力的战区&#xff0c;再去修改定位到那个地区 这样的话打省标、市标这些会降低不少难度&#xff0c;当然靠实力的就不需要这些花里胡哨了 办法步骤需要的参考哦&#xff0c;…