当前位置: 首页 > news >正文

多线程——线程的休眠、中断和等待

目录

 1.线程启动

2.线程休眠

3.线程中断

3.1 自定义变量作为标记

3.2 使用 Thread 类提供的 interrupt() 方法

4.线程等待


上一期讲到线程的创建以及Thread的常见属性,并且提出一些概念如线程休眠、线程中断和线程等待等,这期将会对它们做一个深入理解。

 1.线程启动

其在这在创建线程时就已经介绍,一个线程的创建要调用 start() 方法,然后触发线程的 run() 方法,由JVM调度执行。关于 start() 和 run() 的区别在上期末尾已经总结。

但是,值得注意的是,一个 Thread 对象,只能 start 一次如果有两个或多个 start 同一个 Thread 对象,运行时将会抛出异常 IllegalThreadStateException

2.线程休眠

假设:车间里的工人,人是会疲劳的,但是车间的生产机器可以不停地工作,所以为了机器可以一直工作,工人就需要轮班,这车间里的工位让出来,留给换班的工人。此时工人休息就属于休眠行为。

线程休眠Thread.sleep(时间ms))是 Java 多线程编程中的一个重要机制,它允许当前执行的线程暂停一段时间,让出 CPU 资源给其他线程使用,Thread.sleep(时间),这里的时间是以毫秒(ms)为单位

比如,在前面的代码中,我们不能很好地看到打印结果,也就是 thread 和 main 是怎么执行的,为方便理解,我们可以给代码写一个死循环,并给一定的休眠时间,观察它们的执行顺序。

示例代码:

public class Demo7 {public static void main(String[] args) {Thread thread = new Thread( () -> {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);//以ms为单位,这里是1000ms,相当于休眠1秒} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

运行结果:

从运行结果来看,两个打印是交替打印的。为什么呢?这是因为线程的交替执行是由操作系统的线程调度机制决定的,会根据线程的状态、优先级等交替执行每一个线程。也正是因为线程的调度是不可控的,所以这个休眠时间也不一定真的是每隔一秒才会打印一次。

在线程休眠中,Thread.sleep()这个方法可以传入两个参数,即Thread.sleep(时间1 ms,时间2 ns)时间1和时间2的作用是一样的,表示总休眠时间是时间1 + 时间2前者单位是毫秒,后者单位是纳秒。但是因为线程是不可控的,它实际休眠的时间也并不一定等于设置的时间参数。

或者说:sleep() 的纳秒参数(sleep(ms, ns))通常用于高精度计时,但实际休眠时间受操作系统调度影响,无法保证精确性

3.线程中断

假设:在一生产车间里,工人正在工作,突然车间里的经理发现车间某个角落着火了,但是工人并没有发现,此时此时经理就需要让所有的工人立刻撤离,但是经理无法直接拽走工人,而是让工人赶快撤离,工人听见后,需要立刻停止当前的工作,并赶快安全按撤离。此时经理让工人赶快撤离,就属于中断行为。

线程中断主要有两种方式,一是自定义一个变量来标记进行沟通二是使用 Thread 类提供的 interrupt() 方法。

3.1 自定义变量作为标记

这个方法就类似于经理需要自己去喊工人赶快撤离。

(这里用到一个关键字 volatilevolatile 确保多线程间对变量的可见性,避免编译器优化导致读取旧值,这里知道有这么一个关键字即可,在后面两期会介绍到。)

示例代码:

public class Demo8 {private volatile static boolean isFinished = false;//定义标志public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {while (!isFinished) {System.out.println("hello thread");try {Thread.sleep(1000);//每隔1秒打印一次} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("thread结束!!!");});thread.start();Thread.sleep(5000);//这个线程跑5秒isFinished = true;//更改标志位}
}

不过这种方式,需要注意的是,自定义的标志不能把它定义成局部变量,即这里不能把它定义在 main 方法里,如果定义在 main 方法里,那么这个标志(isFinished)将不能修改,不能修改将导致它进入死循环。为什么呢?

主要原因就是 lambda 表达式,这里创建的线程实际上 在lambda 里面,当希望使用局部变量时,是触发“变量捕获”这样语法,而 lambda 是回调函数,执行的时机可能会在操作系统真正创建出线程之后,而当线程创建好后,可能 main 方法已经执行完毕对应的局部变量也就销毁了为了解决这个问题其实Java会把捕获到的变量临时拷贝一份,拷贝给 lambda ,当局部变量是否销毁也不会影响到 lambda 。但是,拷贝也就意味着重新创建了一个变量也就是当前的 isFinished 其实已经不是最开始定义的所以在这里不能修改所以把这个自定义的标志定义为局部变量时,不仅不能修改,更不能中断程序。(即使这里不是 lambda 表达式创建的线程,而是使用匿名内部类,依然也是同样结果(比如以下的示例代码)。

public class Demo9 {public static void main(String[] args) throws InterruptedException {boolean isFinished = false;//定义标志为局部变量Thread thread = new Thread() {@Overridepublic void run() {while (!isFinished) {System.out.println("hello thread");try{Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("thread结束!!!");}};thread.start();Thread.sleep(5000);//isFinished = true;这里不能修改,如果修改就报错}
}

为什么定义为全局变量就可以呢?这是因为定义为全局变量后不再是“变量捕获”的语法而是切换成了“内部类访问外部类的成员”语法创建的线程相当于内部类定义的标志变量就相当于外部类所以当main方法执行结束后并不会影响这个全局变量也不需要拷贝从始至终都是一个变量所以这里本质就是“内部类访问外部类的成员”语法,自然也就可以修改从而达到中线线程的目的

3.2 使用 Thread 类提供的 interrupt() 方法

Java中 Thread 提供了interrupt() 方法,它交由线程自身检查,如果想让线程中断,人为只需要调用这个方法就可以。这个方法就类似于经理去拉警报器,警告工人赶紧撤离。

示例代码:

public class Demo10 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("thread结束了");});thread.start();Thread.sleep(3000);System.out.println("main结束了");thread.interrupt();}
}

这里抛出的异常是因为线程在 Thread.sleep() 期间被中断(InterruptedException),导致线程异常终止。在这里,如果不希望抛出这个异常,想要代码优雅的跳出循环,就可以把用 break

但是,对于Java来说,如果没有这个 break ,它又会进入应该死循环:

这是因为:sleep() 被中断了,相当唤醒了 sleep ,并修改 isInterrupted(),由于没有 break循环会继续执行,而 !Thread.currentThread().isInterrupted() 会返回 true(因为中断状态已被清除)所以导致线程无法退出,进入死循环。

4.线程等待

假设:你和你的女神约了明天上午一起去逛街,约定的时间是明天早上八点见面。你会非常地激动,于是你明天会准到到达约定地点,但是你的女神,可能因为路上堵车或者早上睡过头了(也有可能放你鸽子但是你不知道),所以没有在规定的时间到达约定的地点。这个时候,你心想好不容易能和女神一起逛街,于是你就会在约定的地点等你的女神,并且你还给自己大气,女神不出现你就线不吃早饭了,你想和女神一起吃早饭。你在等你的女神出现就属于等待行为。

线程等待(join()),就是一个线程需要等待另一个线程完成它的工作后,这个线程才能进行自己的下一步工作

join() 会有异常,所以需要捕获异常(在IDEA中对 sleep 按CTRL + 回车 自动补全),但在这里不是抛出异常,而在 main 方法里声明一个可能出现的异常,即 throws InteruptedException,需要和 sleep() 做一个区分。

代码实例:

public class Demo12 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("thread 结束...");});thread.start();thread.join();System.out.println("main 结束....");}
}

比如上面的代码中,在 main 线程中,调用 thread.join() 方法,这个时候,main 这个线程需要等到 thread 这个线程执行完毕之后,main 这个线程才会执行。

需要着重理解是那个线程等待,哪个线程先执行,可以理解位一个插队行为,比如上面的代码中,main 线程的结束是在最后,因为在main 线程还没结束之前,thread 这个线程插队了。

假如,现在把你的等待条件设置如果女神不出现,你也不走,你心里想着不见不散,那么这个时候你就会一直等下去。显然这是不符合现实的,所以你可能会等上一段时间,一段时间后你的女神还没有出现,那么你就会回去。

所以在线程中,在方法 join() 就可以加一个等待的时间,也就是超时机制,即方法 join(时间ms)。这个方法和线程休眠的方法 sleep() 是类似的,也可以写成 join(时间1 ms,时间2 ns)表示总等待时间是时间1 + 时间2前者单位是毫秒,后者单位是纳秒。但是因为线程是不可控的,它实际等待的时间也并不一定等于设置的时间参数,超时后将不再等待。

public class Demo12 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread");try {Thread.sleep(1000);//每隔1秒打印一次} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("thread结束!!!");});thread.start();thread.join(3600*1000);//等待一小时thread.interrupt();System.out.println("main 结束...");}
}

这个时候我们也可以在 jconsole 中查看线程的信息:

可以发现 main 线程是需要等待执行的,而线程 Thread-0 的等待是说明还没有到结束时间,也就是你还在等你的女神中。main 线程需要等待 thread 线程指定时间后,才会执行。


本篇文章主要探讨线程的启动、休眠、中断和等待等核心操作:

  1. 线程启动:通过start()方法触发线程执行,且每个线程只能启动一次。

  2. 线程休眠Thread.sleep()让线程暂停执行,但实际休眠时间可能受系统调度影响。

  3. 线程中断:通过标志位interrupt()方法通知线程终止。

  4. 线程等待join()实现线程间的等待,可设置超时避免无限等待。


线程经历的启动、休眠、中断和等待,在这个过程中,都经历了哪些状态呢?欲知后事如何,且听下回分解!

http://www.xdnf.cn/news/1423081.html

相关文章:

  • Markdown 语法全面指南
  • Win10系统获取网络上行流量的三种方法
  • 五、导入现有模型
  • 01 2025最新VMware虚拟机下载教程
  • Unity项目基本风格/规范
  • Linux上perf工具的使用-基础采样
  • 命名空间级别应用 Pod 安全标准
  • 从组分到涌现:系统科学视域下结构、功能与层级的辨析及在人工智能中的应用
  • 安全等保复习笔记
  • 大模型 RAG 项目必看:技术架构拆解 + 实战步骤,新手也能快速上手
  • 内存管理 - 从虚拟到物理
  • Java全栈工程师面试实战:从基础到微服务的深度解析
  • CentOS10安装RabbitMQ
  • Spring Bean 生命周期中的 @PostConstruct 注解
  • NestJS 3 分钟搭好 MySQL + MongoDB,CRUD 复制粘贴直接运行
  • 【C++进阶篇】学习C++就看这篇--->多态超详解
  • 传统web项目,vue开发实践篇01
  • 微服务Docker-compose之若依部署
  • 视频提取文字用什么软件好?分享6款免费的视频转文字软件!
  • apipost 8.x 脚本循环调用接口
  • 云手机为什么会受到广泛关注?
  • 单链表的基本原理与实现
  • 深入掌握 Flask 配置管理:从基础到高级实战
  • uniapp使用uview UI,自定义级联选择组件
  • 六、练习3:Gitee平台操作
  • RSA的CTF题目环境和做题复现第1集
  • shell——函数与数组
  • 华东制造企业推荐的SD-WAN服务商排名
  • java中常见的几种排序算法
  • 毕业设计:丹麦电力电价预测预测未来24小时的电价pytorch+lstm+历史特征和价格+时间序列 电价预测模型资源 完整代码数据可直接运行