「JavaEE」线程状态

news/2024/5/18 18:30:35

🎇个人主页:Ice_Sugar_7
🎇所属专栏:JavaEE
🎇欢迎点赞收藏加关注哦!

线程状态

  • 🍉start 和 run 的区别
  • 🍉终止线程
  • 🍉join & 阻塞状态
  • 🍉线程六大状态

🍉start 和 run 的区别

这是一个经典的面试题,以下面代码为例:

public class MyThread extends Thread{@Overridepublic void run() {System.out.println("hello");}public static void main(String[] args) {Thread t = new MyThread();t.start();t.run();}
}

可以看到结果都输出“hello”
在这里插入图片描述
这两者的区别在于:
调用start创建一个新的线程,由这个线程执行打印 hello 的任务;
t.run() 则是调用 Thread 实例中的 run 方法,这个操作是在 main 主线程中打印 hello

如果我们把代码改成下面这样:在 run 方法和 main 方法中写个死循环,此时 t.run() 就只打印 hello thread,主线程没办法再向下执行

public class MyThread extends Thread{@Overridepublic void run() {while(true) {System.out.println("hello thread");}}public static void main(String[] args) {Thread t = new MyThread();t.run();while(true) {System.out.println("hello main");}}
}

🍉终止线程

一个线程,它的 run 方法如果执行完毕,那么它就终止了
如果我们想让线程提前终止,那就需要让 run 方法能够提前结束。我们一般会引入标志位,在其他进程中修改标志位的值来结束进程
也就是说:线程 A 什么时候结束,取决于另一个线程 B 什么时候修改 A 的标志位的值

Thread 实例提供的 currentThread 方法可以用来获取当前线程实例。也就是说哪个线程调用这个方法,得到的就是哪个线程的实例(类似 this)
比如下面这个代码,我们先看 while 循环

while(!Thread.currentThread().isInterrupted()) {     //...
}

isInterrupted 方法是用来查看当前线程是否被中断。如果一个线程被中断,那么得到的结果就为 true,它其实就相当于标志位
通过实例.interrupt() 可以中断线程

public class MyThread{public static void main(String[] args) throws InterruptedException{Thread t = new Thread(()-> {while(!Thread.currentThread().isInterrupted()) {System.out.println("线程运行中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程执行完毕");});t.start();Thread.sleep(3000);//让 t 线程结束t.interrupt();}
}

看下结果:
在这里插入图片描述
可以看到代码出现异常之后,t 线程还在打印,这说明它并没有真正结束
而如果删掉匿名内部类中的 sleep,那么 interrupt 可以让线程顺利结束:

public static void main(String[] args) throws InterruptedException{Thread t = new Thread(()-> {while(!Thread.currentThread().isInterrupted()) {System.out.println("线程运行中");}System.out.println("线程执行完毕");});t.start();Thread.sleep(2000);t.interrupt();
}

在这里插入图片描述

那就说明 sleep 导致结果和预期结果不同
在执行 sleep 的过程中,调用 interrupt,可能会导致 sleep 的休眠时间还没到,就被提前唤醒
被提前唤醒后,会做两件事:
①抛出 InterruptedException (这个异常紧接着就会被 catch 捕获到)
②清除 Thread 对象的 isInterrupted 标志位

在上面的代码中,我们已经通过 Interrupt 方法把标志位设为 true 了,但是 sleep 被提前唤醒后就把标志位设回 false,所以导致循环继续执行
如果想让线程结束,只需在 catch 中加上 break 就 ok 了:

while(!Thread.currentThread().isInterrupted()) {System.out.println("线程运行中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
}

当然,其实不止 sleep 有清空标志位的机制,很多方法都会这样

清空标志位是为了给我们更多的操作空间
比如上一行代码写的是 sleep(1000),但是现在1000ms 还没到就要终止线程,这样就前后矛盾了,此时就需要抛出异常,然后对这种情况进行具体的处理
我们可以在 catch 语句中加入一些代码来做处理:

  1. 让线程立即结束:加上 break
  2. 让线程继续执行:不加 break
  3. 让线程执行一些逻辑之后再结束:写一些其他代码,再 break

idea 生成的 catch 语句里面自动给的代码是 e.printStackTrace(),这个是在打印调用栈,或者是抛出另外一个异常。实际开发中这两种代码只是纯纯占个位置而已,没啥卵用


🍉join & 阻塞状态

虽然多个线程之间的执行顺序是不确定的,但是我们可以在应用程序中通过一些 api 来影响线程执行的顺序

join 就是一种方式,也是线程最核心的 api 之一,它通过影响线程结束的先后顺序来影响总的执行顺序
比如让 main 线程等待 t 线程,那就在 main 线程中调用 t.join()
执行 join 的时候,会看 t 线程是否正在运行
如果 t 正在运行,那么 main 线程就会阻塞(暂时不参与 CPU 执行)
如果 t 运行结束,那么 main 就会从阻塞中恢复过来,继续向下执行

由此可以看出:阻塞使这两个线程的结束时间产生先后顺序

在上面的例子中,就一定是 t 先结束,然后才是 main 结束
实际开发中一般不止 t 和 main 这两个线程,t 线程虽然可能是和其他线程共同进行调度的,但由于主线程一直处于等待状态,所以即使 t 中间经历多次 CPU 的切换,最终也能顺利执行完毕

join 的一个典型应用就是使用多个线程并发进行一系列计算,让一个线程阻塞等待上述计算线程,等到所有线程都计算完了,再让这个线程汇总结果
举个例子,弄两个线程,合作计算 1 到 100w 的和(一个线程计算 50w 个数),最后在 main 线程中打印结果:

public class MyThread{public static long sum = 0;public static void main(String[] args) throws InterruptedException{Thread t1 = new Thread(()-> {for(long i = 1;i <= 50_0000L;i++) sum += i;});Thread t2 = new Thread(()-> {for(long i = 50_0001L;i <= 100_0000L;i++) sum += i;});t1.start();t2.start();t1.join();t2.join();System.out.println(sum);}
}

在这里插入图片描述
在这里插入图片描述
不过这个结果貌似不太对,因为两次算出来的 sum 不一样,这就涉及到后面要讲的线程安全问题,不过这是后话,现在只需知道弄多个线程分别运算的效率,会比单独一个线程运算的效率高就 ok 了

然后我们在调用 join 的时候,可以看到它其实有三个重载的方法

在这里插入图片描述
millis 和 nanos 分别是毫秒和纳秒,不过因为系统的时间没法精确到纳秒级别,所以没啥卵用

如果参数不填时间(也就是第一个重载方法),那称为“死等”,就是说某个线程一定要等到另一个线程执行完才会继续向下执行。但是这种逻辑其实是不科学的,因为如果代码中因为死等导致程序卡住了,那就无法处理后续的逻辑,这就是非常严重的 bug 了
如果参数填了时间,那则是带有超时时间的等待,如果等待的时间超过超时时间,那就不会再等了,继续执行


🍉线程六大状态

Java 中线程的状态可分为:

  1. NEW:已经创建好了 Thread 对象,但是还没有调用 start 方法在系统中创建线程(只有处于 NEW 状态才能 start,并且一个 Thread 对象只能 start 一次)
  2. TERMINATED:系统内部的线程执行完毕
  3. RUNNABLE:就绪状态,表示这个线程正在 CPU 上执行,或者随时都可以去 CPU 上执行
  4. TIMED_WAITING:指定时间的阻塞,到达一定时间之后会自动解除阻塞,使用 sleep 或 带有超时时间的 join 会进入这个状态
  5. WAITING:不带时间的阻塞(死等),必须满足一定条件才会解除阻塞,使用 join 或者 wait 会进入这个状态
  6. BLOCKED:由于锁竞争引起的阻塞(后面说到线程安全时会详细介绍)

可以用一幅图来表示这六个状态间的联系:
在这里插入图片描述

这些状态在我们调试多线程代码的 bug 时可以作为重要参考依据
比如我们常说“程序卡住了”,这就说明一些关键的线程出现阻塞,我们可以通过观察线程的状态分析出一些原因


http://www.mrgr.cn/p/31852506

相关文章

双向循环链表:(创建、插入、遍历、求长、查找、删除、排序、销毁)待测

目录一、双向循环链表存在的意义二、节点的定义三:实现1:创建链表(即创建一个空链表)2:创建新结点3:遍历4:插入头插入尾插入中间插入 一、双向循环链表存在的意义 数组这样的结构提供了连续内存的访问和使用,链表是对内存零碎空间的有效组织和使用,双向循环链表增大了访…

电脑控制手机的工具 挺好用 不收费

# gitee地址 https://gitee.com/Barryda/QtScrcpy?utm_source=alading&utm_campaign=repo#https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fbarry-ran%2FQtScrcpy%2Freleases建议选择国外下载,不用注册码云账号

Fork for Mac v2.42 激活版 Git客户端

Fork for Mac是一款运行在Mac平台上的Git客户端&#xff0c;Fork Mac版具备基本的取、推、提交、修改、创建和删除分支和标签、创建和删除远程备份等功能&#xff0c;还有实用的差异查看器&#xff0c;你可以通过清晰的视图快速发现源代码中的更改。 Fork for Mac v2.42 激活版…

网卡-频段、信道、带宽

频段 在无线通信领域中,网卡频段是指所支持的无线通信频率范围,主要是指2.4G、5G2.4GHz频段:这是最常见的无线通信频段之一,一般用于Wi-Fi网络和蓝牙等。2.4GHz频段在各个地区都是通用的。5GHz频段:这也是用于Wi-Fi网络的一个常见频段,相对于2.4GHz频段,5GHz频段拥有更多…

延迟绑定与retdlresolve

延迟绑定与retdlresolve我们以前在ret2libc的时候,我们泄露的libc地址是通过延迟绑定实现的,我们知道,在调用libc里面的函数时候,它会先通过plt表和gor表绑定到,函数真实地址上,那么在第二次调用的时候就可以用了,不用再次绑定 那么它是怎么样实现的呢,我们还是通过一个…

allegro输出正反面bom

不是前面两条命令&#xff0c;而是component report

网络基础(day3)建议在电脑端注册登陆观看!!!

【 理论重点】 网络是什么&#xff1f; &#xff08;网络是载体&#xff0c;目的是传输互联网中的数据&#xff0c;数据是终端产生<手机、电脑、服务器等>。&#xff09; 如何组件网络&#xff08;良性网络架构&#xff09;&#xff1f;有网络架构思维&#xff0c;得按层…

Golang - 并发同步更新全局切片失败的原因以及解决方案

当多个协程同时访问和修改同一个共享资源(如切片)时,如果没有适当的同步机制,可能会导致数据竞争和不一致的结果。package mainimport ("fmt""sync" )func processChunk(chunk []int64, wg *sync.WaitGroup, failedList []int64) {defer wg.Done()fmt.…

Golang - 同步更新全局切片失败的原因以及解决方案

当多个协程同时访问和修改同一个共享资源(如切片)时,如果没有适当的同步机制,可能会导致数据竞争和不一致的结果。package mainimport ("fmt""sync" )func processChunk(chunk []int64, wg *sync.WaitGroup, failedList []int64) {defer wg.Done()fmt.…

SpringCloud-MQ

【BV1LQ4y127n4】同步通讯和异步通讯 微服务间通讯有同步和异步两种方式。同步通讯就像打电话,需要实时响应;异步通讯就像发邮件,不需要马上回复。两种方式各有优劣,打电话可以立即得到响应,但是却不能跟多个人同时通话。发送邮件可以同时与多个人收发邮件,但是往往响应会…

移动端日志采集与分析最佳实践

前言 做为一名移动端开发者&#xff0c;深刻体会日志采集对工程师来说具有重要意义&#xff0c;遇到问题除了 debug 调试就是看日志了&#xff0c;通过看日志可以帮助我们了解应用程序运行状况、优化用户体验、保障数据安全依据&#xff0c;本文将介绍日志采集的重要性、移动端…

DRF之jwt介绍与使用

一、jwt介绍 1、什么是jwt JWT(JSON Web Token)是一种用于在网络应用中传递信息的开放标准(RFC 7519)。它通过在用户和服务器之间传递的信息生成具有一定结构的令牌,这些令牌可以袐用于身份验证和信息传递。它是一种前后端登陆认证的方案,区别于之前的 cookie,session。…

Milvus 在哈啰的应用与落地

向量数据库还有哪些可能性&#xff1f; 本期的【User Tech】直播告诉你答案&#xff01;明晚的直播&#xff0c;我们邀请了来自哈啰的资深研发工程师王永辉&#xff0c;他将为我们详细讲解 Milvus 在本地出行及生活服务平台的应用及未来发展的诸多可能性&#xff0c;敬请期待&a…

Table表格(关于个人介绍与图片)

展开行&#xff1a; <el-table :data"gainData" :border"gainParentBorder" style"width: 100%"><el-table-column type"expand"><template #default"props"><div m"4"><h3>工作经…

第100+6步 ChatGPT文献复现:ARIMAX预测新冠

基于WIN10的64位系统演示 一、写在前面 我们继续来解读ARIMAX模型文章&#xff0c;这一轮带来的是&#xff1a; 《PLoS One》杂志的2022年一篇题目为《A data-driven eXtreme gradient boosting machine learning model to predict COVID-19 transmission with meteorologic…

NUS EE4408 机器学习 期末一页纸笔记

听老师说每年有几百个上这门课的人 期末考试允许携带一页纸的笔记(虽然最后派不上太大用场 但还是放在这里提供给有需要的同学)

php变量引用

可以看到b原本的值是123,引用a之后,变成100 但是修改b的值为1100之后,发现a的值也从原本的100变成1100了 这就涉及到引用的基本工作原理:当你使用引用时,实际上是在操作同一个变量的不同别名,因此对一个引用的修改会影响到所有指向该变量的引用 应用场景: 如果某个我们…

【Linux】Kill Process 后依然占用显卡空间并显示 No Such Process

问题 &#xff1a; 如图所示&#xff0c;在显卡上使用 Crtl C 结束某个进程后&#xff0c;使用 nvitop 工具或者 nvidia-smi 命令&#xff0c;显示 No Such Process&#xff0c;但是确占用着显卡空间。搜索这个 PID 时&#xff0c;也显示找不到。 解决&#xff1a;实际上是因为…

开启、关闭HDD读、写缓存状态

sg3 一、sg3查看缓存状态 您可以使用sg_modes命令来查看SAS盘和SATA盘的缓存状态。例如,要查看/dev/sdb设备的缓存状态,您可以执行以下命令:sg_modes -p 8,0 /dev/sdb二、sg3关闭机械盘写缓存状态(仅适用于SAS盘) 对于SAS盘,您可以按照以下步骤更改其读写缓存状态: 1、编…