【多线程】认识Thread类及其常用方法
📄前言:
本文是对以往多线程学习中 Thread类 的介绍,以及对其中的部分细节问题进行总结。
文章目录
- 一. 线程的 创建和启动
- 🍆1. 通过继承 Thread 类创建线程
- 🍅2. 通过实现 Runnable 接口创建线程
- 🥦3. 其他方法创建线程(本质上为上面两种写法的变形)
- 🥑3.1 使用 Thread 的匿名内部类
- 🥬3.2 使用匿名内部类实现 Runnable 接口
- 🥒3.3 使用 lambda 表达式实现 Runnable 接口 (推荐,更加简洁!!!)
- 🍉4. 线程的启动(关于 start方法 和 run方法 的思考)
- 二. Thread类的属性和常用方法
- 🍚1. Thread类的主要属性
- 🍥2. 线程休眠
- 🍭3. 线程等待
- 🍦 4. 获取线程实例
- 🧊5. 线程的中断 (关于线程中断的细节!!!)
之前的文章介绍过线程的引入能够更好地处理程序的并发执行问题。在Java中,线程的创建方式之一是通过 Thead类 (Thead封装了操作系统提供的API,使我们创建的线程能够系统的调度)。接下来我们先了解一下线程的创建方法。
一. 线程的 创建和启动
通过 Thread类 创建线程的方式总体来说可以分为以下两种:
- 继承 Thread类,重写类中的 run() 方法
- 实例化Thread类,实现 Runnable 接口,重写接口中的 run() 方法(通过Thread的构造方法传参 间接重写run() 方法)
这里我们应该可以发现一个共同点,这两种方法都需要重写 run() 方法,毫无疑问 run() 方法就是线程创建后执行代码逻辑的 “入口”方法,通过查看源码我们发现 Thread类 继承了Runnable,而 run() 方法就是该接口中的抽象方法。(如下)


🍆1. 通过继承 Thread 类创建线程
class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("hello thread!");}System.out.println("线程结束!");}
}
🍅2. 通过实现 Runnable 接口创建线程

class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("hello thread !");}System.out.println("线程结束 !");}
}public class Demo2 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(new MyRunnable());}}
🥦3. 其他方法创建线程(本质上为上面两种写法的变形)
🥑3.1 使用 Thread 的匿名内部类
Thread t = new Thread(){@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("hello thread !");}System.out.println("线程结束 !");}
};
🥬3.2 使用匿名内部类实现 Runnable 接口
Thread t = new Thread(new Runnable() {@Overridepublic void run() {while(true) {System.out.println("hello thread !");}System.out.println("线程结束 !");}
});
🥒3.3 使用 lambda 表达式实现 Runnable 接口 (推荐,更加简洁!!!)
Thread t = new Thread(() -> {while(true) {System.out.println("hello thread !");}System.out.println("线程结束 !");
});
🍉4. 线程的启动(关于 start方法 和 run方法 的思考)
其实线程的启动方式很简单,直接调用 Thread类中的 start()方法 即可启动线程,为了观察新创建的线程和 “主线程main” 的并发执行效果,代码中调用 Thread类的类方法 sleep(), 让每次语句执行后休眠 1s。(代码及程序运行效果如下)
public static void main(String[] args) throws InterruptedException {Thread t = 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("线程启动 !");t.start();// 主线程for(int i = 0; i < 5; i++) {System.out.println("hello main !");Thread.sleep(1000);}
}

通过上面代码的执行结果我们容易知道:创建的线程执行的代码其实就是 run() 方法中的代码,那么我们是否可以通过执行 t.run() 来代替 t.start() 呢?
答案很明显是否定的,因为直接调用 run() 方法本质上与一个自定义函数的调用并无任何差异。我们都知道,在程序的某处调用一个函数,程序会在该函数执行结束后才继续执行后面的代码,因此不能使用 t.run() 来代替 t.start()。
总结:调用run() 方法只是一次简单的函数调用,程序依旧是顺序执行;只有调用 start() 才能在操作系统真正创建一个主线程并发执行的线程。
二. Thread类的属性和常用方法
🍚1. Thread类的主要属性

注意:所有创建的线程默认为前台线程,只有当所有前台线程运行结束后,程序才会结束,主线程main运行结束,不会影响其他前台线程的运行。(可以通过setDaemon()方法将线程设置为后台线程)
🍥2. 线程休眠

代码示例及程序运行结果如下:让新线程每1s进行一次打印,主线程每2s进行一次打印(注意:使用sleep()方法需要处理可能抛出的异常)
public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 0; i < 6; i++) {System.out.println("hello thread !");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();for (int i = 0; i < 3; i++) {System.out.println("hello main !");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}
}

🍭3. 线程等待

代码实例和程序运行结果如下:让主线程进行一次打印后,等待新线程结束再继续运行。
public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("hello thread !");}});t.start();for (int i = 0; i < 5; i++) {System.out.println("hello main !");if(i == 0) {try {t.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

🍦 4. 获取线程实例
在线程的创建过程中,由于 Thread类 还没有被实例化,因此不能通过类对象的引用变量直接得到该对象,而应该使用 Thread类提供的方法得到该对象的引用。

🧊5. 线程的中断 (关于线程中断的细节!!!)
在正式了解线程中断的方法之前,我们需要知道一个基本的事实:
Java中线程的中断方式并不是让一个正在运行的线程直接中止,而是以一种 “通知的方式” 告诉线程,“你当前应该结束线程了”,而具体是否立即结束当前该线程,由线程接收通知后的代码处理逻辑决定。
==========
关于线程的中断方法,Thread类 提供了以下三个方法:

Thread类收到通知的方式有以下两种情况:
- 当前线程因 sleep/join/wait 等方法引起阻塞而挂起,以 InterruptedException 异常的形式通知,并清除中断标志,此时线程的阻塞状态立即结束,而是否立即结束由 try/catch 中catch的处理逻辑决定。(注意:线程是否有后续的处理权,取决于该线程是否有能引起阻塞状态的代码)
- 当前线程处于正常运行状态,将中断标志位设置为true。
==========
- 当线程处于阻塞状态时,存在以下三种对应的处理方式:
1)不管这个通知,继续运行。 2)直接结束线程。 3)进行一些特定的收尾工作再结束线程
public static void main(String[] args) {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread !");try {Thread.sleep(1000);} catch (InterruptedException e) {// 1. 无视通知e.printStackTrace();// 2. 直接退出// break;// 3. 进行收尾工作,再退出// System.out.println("此处是后续的处理代码");// break;}}});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}t.interrupt();
}



看到第一个程序运行的结果,不知道大家会不会有一个疑惑,使用 isInterrupted()方法是不会清除标志位的,那 3s过后该线程不应该只打印3次 “hello thread !”,接着由于循环的判断条件为 false 而直接退出吗?
其实 isInterrupted() 方法调用后确实不会清除标志位,程序继续运行的原因是因为3s后主线程调用interrupt()方法,解除了 t线程 的阻塞状态,同时 sleep() 引起的异常会顺便清除标志位。
因此,如果将代码改为下面的写法,线程会在当前代码逻辑执行完毕 或 进入下一次循环判断后直接退出。
public static void main(String[] args) {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread !");}System.out.println("---3s后 t线程退出---");});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}t.interrupt();
}
程序运行结果如下:

======
- 当前线程处于正常运行状态,将中断标志位置为true。
从前面我们可以知道:Thread.interrupted() 判断后会清除标志位,isInterrupted() 不会清除标志位,现有以下代码:
private static int count = 100;public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 1; count > 0 || Thread.currentThread().isInterrupted(); i++) {if(Thread.currentThread().isInterrupted()) {System.out.println("我当前收到中断通知了,这是第 " + i + " 次打印");} else {System.out.println("我当前没有收到中断通知, 这是第 " + i + " 次打印");}count--;if(i == 150) break;}});t.start();try {Thread.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}t.interrupt();
}
程序的运行结果如下:

这段代码的运行结果是:程序在第58次打印时标志位发生了改变,并且后续的打印不再发生改变,可以预见的是:如果没有使用 i 的值判断使循环退出,程序将会无终止地进行打印。原因就是 isInterrupted()方法 不会清除标志位,因此判断条件永远为真。
若把循环中的方法修改为 Thread.interrupted(),程序的运行结果就发生了改变:
private static int count = 50;public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 0; count > 0 || Thread.interrupted(); i++) {if(Thread.interrupted()) {System.out.println("我当前收到中断通知了,这是第 " + i + " 次打印");} else {System.out.println("我当前没有收到中断通知, 这是第 " + i + " 次打印");}count--;}});t.start();try {Thread.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}t.interrupt();
}

可以看到在第27次打印时标志位发生了改变,说明此时线程收到了中断通知,但后面的打印内容为“没有收到通知”,且程序最终打印了50次便结束了,这都说明了调用 Thread,interrupted()方法 后标志位被清除了。
以上就是本篇文章的全部内容了,如果这篇文章对你有些许帮助,你的点赞、收藏和评论就是对我最大的支持。
另外,文章可能存在许多不足之处,也希望你可以给我一点小小的建议,我会努力检查并改进。
相关文章:
【多线程】认识Thread类及其常用方法
📄前言: 本文是对以往多线程学习中 Thread类 的介绍,以及对其中的部分细节问题进行总结。 文章目录 一. 线程的 创建和启动🍆1. 通过继承 Thread 类创建线程🍅2. 通过实现 Runnable 接口创建线程🥦3. 其他方…...
多用户商业版 whisper 2.1在线搭建教程
1. 准备工作 购买许可证:确保你已经购买了足够数量的用户许可证,以便所有员工或客户都能使用软件。系统要求:检查你的服务器和客户端计算机是否满足软件的最低系统要求。网络配置:确保你的网络环境(如防火墙、路由器等…...
HEXO搭建个人博客
Hexo是一款基于Node.js的静态博客框架,可以生成静态网页托管在GitHub上。中文文档见HEXO 配置环境 安装Git:下载并安装Git 检查git是否正确安装: git --version 安装Node.js:Node.js 为大多数平台提供了官方的安装程序。注意安装…...
Spring MVC学习之——RequestMapping注解
RequestMapping注解 作用 用于建立请求URL和处理请求方法之间的对应关系。 属性 value:指定请求的实际地址,可以是一个字符串或者一个字符串列表。 value可以不写,直接在括号中写,默认就是value值 RequestMapping(value“/hel…...
鸿蒙原生应用/元服务开发-延迟任务开发实现(二)
一、接口说明 接口名接口描述startWork(work: WorkInfo): void;申请延迟任务stopWork(work: WorkInfo, needCancel?: boolean): void;取消延迟任务getWorkStatus(workId: number, callback: AsyncCallback>): void;获取延迟任务状态(Callback形式)g…...
机器学习在什么场景下最常用-九五小庞
机器学习在多个场景中都有广泛的应用,下面是一些常见的应用场景: 自然语言处理(NLP):如语音识别、自动翻译、情感分析、垃圾邮件过滤等。数据挖掘和分析:如市场分析、用户画像、推荐系统、欺诈检测等。智能…...
利用IP应用场景API识别真实用户
引言 在当今数字化时代,随着互联网的普及和应用的广泛,验证用户身份的重要性变得越来越突出。在许多场景中,特别是在涉及安全性、用户体验以及个人隐私保护方面,确定用户的真实身份至关重要。而IP应用场景API则是一种强大的工具&…...
Hugging Face怎么通过国内镜像去进行模型下载(hf-mirror.com)
一、引言 Hugging Face 🤗是一家专注于自然语言处理(NLP)技术的公司,以其开源贡献和先进的机器学习模型而闻名。该公司最著名的产品是 Transformers 库,这是一个广泛使用的 Python 库,它提供了大量预训练模…...
POKT Network 开启周期性通缩,该计划将持续至 2025 年
POKT Network(也被称为 Pocket Network)在通证经济模型上完成了重大的改进,不仅将通货膨胀率降至 5% 以下,并使 POKT 通证在 2025 年走向通缩的轨迹上,预计到2024 年年底通货膨胀率将降至 2% 以下。POKT Network 的 “…...
LRU Cache
文章目录 1. 什么是LRU Cache2. LRU Cache的实现3. LRU Cache的OJ题目分析AC代码 1. 什么是LRU Cache LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache? 狭义的Cache指的是位于CPU和主存间的快速RAM…...
软件测试面试题整理
软件测试的几个阶段 在进行Beta测试之前和之后,通常会进行以下几种测试: 内部测试(Internal Testing) 在Beta测试之前,开发团队会进行内部测试,对软件进行全面的测试。这个阶段包括单元测试、集成测试和系…...
C++三剑客之std::variant(二):深入剖析
目录 1.概述 2.辅助类介绍 2.1.std::negation 2.2.std::conjunction 2.3.std::is_destructible 2.4.std::is_object 2.5.is_default_constructible 2.6.std::is_trivially_destructible 2.7.std::in_place_type和std::in_place_index 3.原理分析 3.1.存储分析 3.2.…...
实验一 安装和使用Oracle数据库
🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…...
软件工程研究生后期总结
写这篇随笔的时候,我已经处于研究生阶段的后期,只剩下一个硕论答辩即可结束研究生生涯。趁有闲暇时间,我希望可以从实习、兼职、论文和求职等几个角度重新整理一下研究生后期的工作和收获,以及对未来工作和生活做出展望。 首先简…...
Java爬虫爬取图片壁纸
Java爬虫 以sougou图片为例:https://pic.sogou.com/ JDK17、SpringBoot3.2.X、hutool5.8.24实现Java爬虫,爬取页面图片 项目介绍 开发工具:IDEA2023.2.5 JDK:Java17 SpringBoot:3.2.x 通过 SpringBoot 快速构建开发环境…...
红队打靶练习:HOLYNIX: V1
目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto whatweb 目录探测 1、gobuster 2、dirsearch 3、dirb 4、feroxbuster WEB sqlmap 1、爆库 2、爆表 3、爆列 4、爆字段 后台登录 1、文件上传 2、文件包含 3、越权漏洞 反弹shell 提权 总结 信息…...
elasticsearch[二]-DSL查询语法:全文检索、精准查询(term/range)、地理坐标查询(矩阵、范围)、复合查询(相关性算法)、布尔查询
ES-DSL查询语法(全文检索、精准查询、地理坐标查询) 1.DSL查询文档 elasticsearch 的查询依然是基于 JSON 风格的 DSL 来实现的。 1.1.DSL 查询分类 Elasticsearch 提供了基于 JSON 的 DSL(Domain Specific Language)来定义查…...
Microsoft Word 设置底纹
Microsoft Word 设置底纹 References 打开文档页面,选中特定段落或全部文档 在“段落”中单击“边框”下三角按钮 在列表中选择“边框和底纹”选项 在“边框和底纹”对话框中单击“底纹”选项卡 在图案样式和图案颜色列表中设置合适颜色的底纹,单击“确…...
【大数据】Flink 详解(九):SQL 篇 Ⅱ
《Flink 详解》系列(已完结),共包含以下 10 10 10 篇文章: 【大数据】Flink 详解(一):基础篇【大数据】Flink 详解(二):核心篇 Ⅰ【大数据】Flink 详解&…...
workflow源码解析:GoTask
关于go task 提供了另一种更简单的使用计算任务的方法,模仿go语言实现的go task。 使用go task来实计算任务无需定义输入与输出,所有数据通过函数参数传递。 与ThreadTask 区别 ThreadTask 是有模板,IN 和 OUT, ThreadTask 依赖…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
