多线程的Thread 类及方法
✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇
✨每日一语:海压竹枝低复举,风吹山角晦还明。
目 录
- 🌲一. 线程的复杂性
- 🌴二. Thread 类及常见方法
- 📕2.1 Thread 的常见构造方法
- 📗2.2 Thread 的几个常见属性
- 📘2.3 启动一个线程-start()
- 📙2.4 中断一个线程
- 📓2.5 等待一个线程-join()
- 📔2.6 获取当前线程引用
- 📒2.7 休眠当前线程
- 🌳三. 线程的状态
- 🌻3.1 线程的所有状态
- 🌹3.2 线程状态和状态转换
🌲一. 线程的复杂性
多线程是非常复杂的,以至于程序猿为了规避多线程代码而发明了很多其他的方法,来实现并发编程。
最复杂的地方实际上在于操作系统的调度执行
例如:主线程运行创建新线程,主线程中要完成 abcdef 个任务,而新线程要完成 123456 个任务
情况一:(时间线从上到下,按照时间先后顺序执行)
情况二:(时间线从上到下,按照时间先后顺序执行)
情况三:(时间线从上到下,按照时间先后顺序执行)
还有许许多多的情况,远远不止上面三种,多线程最复杂的地方就在于操作系统 的 "随机" 调度,也可以理解为线程的创建是有先后顺序的,但是执行线程先后顺序是随机的!!!具体这个线程里的任务啥时候执行要看调度器!!!
🌴二. Thread 类及常见方法
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
📕2.1 Thread 的常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
【了解】Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(“这是我的名字”);
Thread t4 = new Thread(new MyRunnable(), “这是我的名字”);
Thread(String name) --> 给线程起名字!线程在操作系统内核里,是没有名字的,只有一个身份标识,但是在 Java 中,为了能让程序猿调试的时候方便理解这个线程是谁,就在 JVM 中对应的 Thread 对象加了个名字(多个线程名字可以相同,不取名字也会有默认的名字)
public class Demo7 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}," 我的线程 ");t.start();while (true){System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
例如上述代码中我们把线程命名为 " 我的线程 " 之后,我们可以根据上篇博客查看进程信息,在这个地方省略步骤。
如上我们就是进程命名成功了。
📗2.2 Thread 的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题后续详解
getId() --> 线程在 JVM 中的身份标识
线程的身份标识有好几个
- 内核的 PCB 上有标识
- 到了用户态线程库里也有标识。(pthread,操作系统提供的线程库)
- 到了 JVM 中又有一个标识(JVM Thread 类底层也是调用操作系统的 pthread 库)
标识各不相同,但是目的都是作为身份的区别
getName() --> 在 Thread 构造方法里传入的名字
getState() --> PCB 里有个状态,此处得到的状态是 JVM 里面设立的状态体系,比操作系统里的状态体系要丰富一些
getPriority() --> 获取到优先级
isDaemon() --> 守护线程(后台线程)。类似于手机 APP 前后台,线程分为前台线程和后台线程。一个线程创建出来默认是前台线程,前台线程会阻止进程结束,进程会保证所有的前台线程都执行完了才会退出;后台线程不会阻止进程结束,进程退出的时候不管后台线程是否执行完。 main线程就是一个前台进程,通过 t.setDaemon(true); 把线程设置为后台线程。
public class Demo8 {public static void main(String[] args) {Thread t = new Thread(() ->{while(true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"我的线程");t.start();System.out.println(t.getId());System.out.println(t.getName());System.out.println(t.getState());System.out.println(t.getPriority());System.out.println(t.isDaemon());System.out.println(t.isAlive());}
}
上述操作都是获取到一瞬间的状态,不是持续状态
📘2.3 启动一个线程-start()
创建 Thread 实例,并没有真的在操作系统内核里创建出线程!调用 start 才是真正在系统里创建出新的线程,才真正开始执行任务!调用 start 方法, 才真的在操作系统的底层创建出一个线程。
创建 Thread 实例 --> 安排任务,各就各位。任务内容的体现在于 Thread 的 run 或者 Runnable 或者 lambda 。
线程的执行结束:只要让线程的入口方法(run,Runnable,lambda)执行完了,线程就随之结束了。(主线程的入口方法,就可以视为是 main 方法)
📙2.4 中断一个线程
- 手动创建标志位,来区分线程是否要结束
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.
public class Demo9 {//用一个布尔变量表示线程是否要结束//这个变量是一个成员变量,而不是局部变量private static boolean isQuit = false;public static void main(String[] args) {Thread t = new Thread(()->{while(!isQuit){System.out.println("线程运行中...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("新线程执行结束!");});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("控制新线程退出!");isQuit = true;}
}
如上代码中控制线程结束,主要是这个线程有个循环,这个循环执行完毕就结束了。很多时候创建线程都是让线程完成一些比较复杂的任务,往往都是有一些循环(正是有这些循环,执行的时间才可能比较长一点),如果线程本身执行的很快,刷一下就完了,也就没有必要提前控制他结束的必要了。
Thread 中有内置的标志位,不需要咱们手动创建
- 使用 Thread 自带的标志位
在循环条件中改一下即可
!Thread.currentThread().isInterrupted()
currentThread() --> 静态方法,获取到当前线程的实例(Thread 对象),这个方法总是会有一个线程调用他。线程 1 调用这个方法,就能返回线程 1 的 Thread 对象;线程 2 调用这个方法,就能返回线程 2 的 Thread 对象。
isInterrupted() --> 判定内置的标志位,为 true 表示线程要被中断,要结束。
public class Demo10 {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();}}});t.start();Thread.sleep(5000);System.out.println("控制线程退出!");t.interrupt();}
}
调用 interrupt,产生了一个异常!异常出现了,线程还在运行!
注意:interrupt 方法的行为
如果 t 线程没有处在阻塞状态,此时 interrupt 就会修改内置的标志位
如果 t 线程正在处于阻塞状态,此时 interrupt 就让线程内部产生阻塞的方法,例如 sleep 抛出异常,interruptedException。
此处异常被 catch 捕获了,捕获之后,什么都没做,就只是打印个调用栈就完了,因此把异常的打印忽略即可。
正是因为这样的操作,程序猿就可以自行控制线程的退出行为了
- 可以立即退出
break;
- 可以等一会儿退出
try {Thread.sleep(1000);
} catch (InterruptedException ex) {ex.printStackTrace();
}
break;
- 可以不退出
啥也不做,相当于忽略了异常
主线程发出 “退出” 命令时,新线程自己来决定如何处理这个退出行为
综上:Java 中终止新线程
线程阻塞:1. 立即停止 2. 待会停止 3.啥也不做
线程没有阻塞:通过标志位直接停止
📓2.5 等待一个线程-join()
线程之间的执行顺序是完全随机的,看系统的调度!我们不能确定两个线程的开始执行顺序,但是可以控制两个线程结束的顺序!
join 的顺序谁先调用谁后调用是无所谓的
t1.join();
t2.join();
如上先调用 t1.join 后调用 t2.join ,此时 t1 t2 开始运行了,main 先阻塞在 t1 这里
- 如果是 t1 先结束,t2 后结束。当 t1 结束的时候,main这里的 t1.join 就执行完毕了,继续执行 t2.join,就阻塞了,然后 t2 结束的时候,main 的 t2.join 也返回(结束阻塞),main 继续执行,完成计时操作。
- 如果是 t2 先结束,t1 后结束。当 t2 结束的时候,由于 t1 没结束,main 仍然在 t1.join 这里阻塞,当 t1 结束之后,t1.join 解除阻塞,main 继续执行到 t2.join,由于 t2 已经结束了,t2.join 就不再阻塞了,直接返回,main继续执行
- 实现让 t2 等待 t1执行完,main 等待 t2 执行完
public class Demo11 {private static Thread t1 = null;private static Thread t2 = null;public static void main(String[] args) {t1 = new Thread(()->{System.out.println("t1 begin");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t1 end");});t1.start();t2 = new Thread(()->{System.out.println("t2 begin");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2 end");});t2.start();try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main end");}
}
- 主线程先执行 t1 然后 join 等待 t1 执行完毕,t1 执行完之后,主线程再启动 t2 等待 t2 执行完毕
public class Demo12 {public static void main(String[] args) throws InterruptedException {System.out.println("main begin");Thread t1 = new Thread(()->{System.out.println("t1 begin");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t1 end");});t1.start();t1.join();Thread t2 = new Thread(()->{System.out.println("t2 begin");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2 end");});t2.start();System.out.println("main end");}
}
join 的行为:
- 如果被等待的线程还没执行完,就阻塞等待
- 如果被等待的线程已经执行完了,直接就返回
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
- 第一个是死等
- 第二个和第三个设定了最大等待时间
- 第三个的俩参数第一个是毫秒,第二个是纳秒,然后就是总的时间
未来实际开发程序的时候一般不会使用死等,会因为小的代码 bug 会让服务器卡死导致无法继续工作
📔2.6 获取当前线程引用
为了对线程进行操作(线程等待,线程中断,获取各种线程的属性),就需要获取到线程的引用
- 如果是继承 Thread,然后重写 run 方法,可以直接在 run 方法中使用 this 即可获取到线程的实例,但是如果是 Runnable 或者 lambda,this 就不行了(this 就不是指向 Thread 实例)
- 更通用的办法,Thread.currentThread()。哪个线程来调用这个方法,得到的结果就是哪个线程的实例。
📒2.7 休眠当前线程
- 使用 sleep
在操作系统内核中有就绪队列(这里的PCB随时可以去CPU上执行),还有阻塞队列(这里的PCB暂时不参与调度,不去CPU上执行)。当某个代码中的线程调用 sleep 这个线程就从就绪队列跑到阻塞队列中,暂时不参与调度,等待 sleep 时间执行结束,然后就会调回就绪队列中(不是立马上 CPU 执行,还得看调度器的情况)。
🌳三. 线程的状态
🌻3.1 线程的所有状态
- NEW: 安排了工作, 还未开始行动(创建了 Thread 对象,但是还没有调用 start 方法,系统内核里还没有线程)
- RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作(就绪状态:1. 正在 CPU 上运行;2. 还没在 CPU 上运行,但是一切准备就绪)
- BLOCKED: 这几个都表示排队等着其他事情(等待锁)
- WAITING: 这几个都表示排队等着其他事情(线程中调用了 wait)
- TIMED_WAITING: 这几个都表示排队等着其他事情(线程中通过 sleep 进入的阻塞)
- TERMINATED: 工作完成了(系统里面的线程已经执行完毕,销毁了,相当于线程的 run 执行完了,但是 Thread 对象还在)
public class Demo13 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});//start 之前获取,获取到的是线程还未创建的状态System.out.println(t.getState());t.start();Thread.sleep(500);//加上之后就是由 RUNNABLE 改成了 TIMED_WAITINGSystem.out.println(t.getState());t.join();//join 之后获取,线程结束之后的状态System.out.println(t.getState());}
}
Thread.sleep(500);之前:(正在工作中)
Thread.sleep(500);之后:(正在 sleep 中)
🌹3.2 线程状态和状态转换
看起来复杂,简化起来就很简单:
主干道是 NEW => RUNNABLE => TERMINATED
在 RUNNABLE 会根据特定的代码进入支线任务,这些支线任务都是 "阻塞状态",这三种阻塞状态,进入的方式不一样,同时阻塞的时间也不同,被唤醒的方式也不同。
相关文章:

多线程的Thread 类及方法
✨个人主页:bit me👇 ✨当前专栏:Java EE初阶👇 ✨每日一语:海压竹枝低复举,风吹山角晦还明。 目 录🌲一. 线程的复杂性🌴二. Thread 类及常见方法📕2.1 Thread 的常见构…...

QT入门Item Views之QTreeView
目录 一、QTreeView界面相关 1、布局介绍 二、基本属性功能 1、设置单元格不能编辑 2、一次选中一个item 3、去掉鼠标移动到单元格上的虚线框 4、最后一列自适应 三、代码展示 1、创建模型,导入模型 2、 右键菜单栏 3、双…...

Servlet | Servlet简单入门——构建第一个Servlet项目
本专栏主要是记录学习JavaWeb中的Servlet相关知识点,如果刚开始学习Java的小伙伴可以点击下方连接查看专栏 本专栏地址:🔥Servlet Java入门篇: 🔥Java基础学习篇 Java进阶学习篇(持续更新中)&am…...

Spring的IOC/DI,依赖注入的实现
Spring的IOC/DI,依赖注入的实现 https://download.csdn.net/download/weixin_41957626/87546826 资源地址 1.什么是Spring 1.1spring3 的体系结构图 图1 spring3的体系结构图 图2 spring4体系结构图 比较spring3的体系结构图,spring4去掉了spring3中的st…...

【tensorflow onnx】TensorFlow2导出ONNX及模型可视化教程
文章目录1 背景介绍2 实验环境3 tf2onnx工具介绍4 代码实操4.1 TensorFlow2与ONNX模型导出4.2 ONNX正确性验证4.3 TensorFlow2与ONNX的一致性检查4.4 多输入的情况4.5 设定输入/输出节点5 ONNX模型可视化6 ir_version和opset_version修改7 ONNX输入输出维度修改8 致谢原文来自于…...

天梯赛训练L1-013--L1-015
目录 1、L1-013 计算阶乘和 2、L1-014 简单题 3、L1-015 跟奥巴马一起画方块 1、L1-013 计算阶乘和 分数 10 题目详情 - L1-013 计算阶乘和 (pintia.cn) 对于给定的正整数N,需要你计算 S1!2!3!...N!。 输入格式: 输入在一行中给出一个不超过10的正…...

进程(操作系统408)
进程的概念和特征 概念: 进程的多个定义: 进程是程序的一次执行过程 进程是一个程序及其数据在处理机上顺序执行时所发生的活动 进程时具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位 上面所说…...

浅谈运维工程师的开发能力的培养
写在前面 本文已获得作者授权,作者的博客地址:https://www.cuiliangblog.cn/ 原文链接:浅谈运维工程师的开发能力的培养 一、运维工程师发展路线 1. 传统运维 侧重点是解决具体的问题。要求具备扎实的底层的知识储备,如网络、l…...

Netcode升级到1.2.0网络变量的变化的变化
Netcode升级到1.2.0网络变量的变化1 概述2 继承网络变量 NetworkVariable,派生类构造出错的问题2.1 代码描述2.2 问题记录2.3 解决办法:使用 NetworkVariable 即可3 网络变量 NetworkVariable 类的版本差异比较3.1 差异说明3.2 [1.0.2]版本的网络变量3.3…...

冥想第七百二十二天
1.周六去给朋友讲了一天的软件,给朋友带了2袋面包边,几袋方便面。感谢朋友的款待,做的蒸菜双拼,柠檬风爪,排骨汤,汤圆,牛肉,孜然回锅肉。 2.讲到下午五点,就回去了。感觉…...

AB测试——流程介绍(定义问题和指标选取)
前言: 作为AB测试的学习记录,本文主要介绍了AB测试的基本流程,以及指标类型和如何选取合适指标。 相关文章:AB测试——原理介绍 AB测试的基本流程是什么? AB测试(也称为分流测试)是一种常用的实…...

Linux(Centos)安装Minio集群
目录1:简介2:功能与集成3:架构4:搭建集群4.1:挂载磁盘4.1.1:要求4.1.2:创建挂载目录4.1.3:注意:需要将新建的目录挂在到对应的磁盘下,磁盘不挂载好,集群启动会…...

LeetCode 1662. 检查两个字符串数组是否相等 / 795. 区间子数组个数 / 剑指 Offer 47. 礼物的最大价值
1662. 检查两个字符串数组是否相等 2022.11.1 新的一月又开始了 题目描述 给你两个字符串数组 word1 和 word2 。如果两个数组表示的字符串相同,返回 true ;否则,返回 false 。 数组表示的字符串 是由数组中的所有元素 按顺序 连接形成的…...

【C++】缺省参数函数重载
🏖️作者:malloc不出对象 ⛺专栏:C的学习之路 👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈 目录前言一、缺省参数1.1 缺省参数的概念1…...

Hbuilder 下载与安装教程
文章目录Hbuilder下载与安装教程Hbuilder简介一,下载Hbuilder二,安装Hbuilder三,简单使用四,Hbuilderx 调试Hbuilder下载与安装教程 Hbuilder简介 Builder是DCloud(数字天堂)推出的一款支持HTML5的Web开发…...

Mybatis工程升级到FlunetMybatis后引发的问题以及解决方法
0. 背景交代为了提高开发速度,我打算将公司原有Mybatis框架升级为FlunetMybatis。可是遇到了一系列问题,下面开始爬坑工程结构示意如下:src/ ├── main │ ├── java.com.demo │ │ ├── Application.java //S…...

Oracle VM VirtualBox6.1.36导入ova虚拟机文件报错,代码: E_INVALIDARG (0x80070057)
问题 运维人员去客户现场部署应用服务,客户是windows server 服务器(客户不想买新机器),我们程序是在linux系统里运行(其实windows也可以,主要是为了保持各地环境一致方便更新和排查问题)我们使…...

Superset数据探索和可视化平台入门以及案例实操
1、Superset背景 1.1、Superset概述 Apache Superset是一个现代的数据探索和可视化平台。它功能强大且十分易用,可对接各种数据源,包括很多现代的大数据分析引擎,拥有丰富的图表展示形式,并且支持自定义仪表盘。 1.2、环境说明 …...

VisualSP Enterprise - February crack
VisualSP Enterprise - February crack VisualSP(可视化支持平台)提供了一个上下文中完全可定制的培训平台,它可以作为企业web应用程序的覆盖层提供。无论员工正在使用什么应用程序,他们都能够快速访问页面培训和指导,说明如何最有效地使用该…...

004+limou+HTML——(4)HTML表格
000、前言 表格在实际开发中的应用还是比较多的,表格可以更加清晰地排列数据 001、基本结构 (1)构成 表格:<table>行:<tr>(table row,表格行),由多少组t…...

uniapp实现自定义相机
自定义相机起因由于最近用uniapp调用原生相机容易出现闪退问题,找了很多教程又是压缩图片又是优化代码,我表示并没有太大作用!!实现自定义相机使用效果图拓展实现多种自定义相机水印相机身份证相机人像相机起因 由于最近用uniapp调用原生相机容易出现闪退…...

插值多项式的龙格现象的介绍与模拟
在文章拉格朗日插值多项式的原理介绍及其应用中,笔者介绍了如何使用拉格朗日插值多项式来拟合任意数据点集。 事实上,插值多项式会更倾向于某些形状。德国数学家卡尔龙格Carl Runge发现,插值多项式在差值区间的端点附近会发生扭动&#x…...

Spring整体架构包含哪些组件?
Spring是一个轻量级java开源框架。Spring是为了解决企业应用开发的复杂性而创建的,它使用基本的JavaBean来完成以前只可能由EJB完成的事情。 Spring的用途不仅限于服务器端的开发,从简单性、可测试性和松耦合的角度而言,任何java应用都可以从…...

开发接口需要考虑哪些问题?
1 接口名字 user/ user/adduser/xxx 见名知意,调用接口的开发人员和后来接手的开发人员能够根据接口名称大致猜测出接口作用。 2 协议 设计接口时,应明确调用接口的协议,是采用HTTP协议,HTTPS协议还是FTP协议。比如跨语言调用通常使用WebS…...

关于Activiti7审批工作流绘画流程图(2)
文章目录一、25张表详解二、安装插件一.定制流程提示:以下是本篇文章正文内容,下面案例可供参考 一、25张表详解 虽然表很多,但是仔细观察,我们会发现Activiti 使用到的表都是 ACT_ 开头的。表名的第二部分用两个字母表明表的用…...

String.format()对日期进行格式化
前言:String.format()作为文本处理工具,为我们提供强大而丰富的字符串格式化功能,这里根据查阅的资料做个学习笔记,整理成如下文章,供后续复习查阅。一. format()方法的两种重载形式:format(String format,…...

核酸检测信息管理系统
目录前言一、功能与需求分析二、详细设计与实现1、data包(1)DataDataBase(2)NaPaNamePassword2、operation包(1)操作接口(2)Resident用户功能(3)Simper用户功…...

典型回溯题目 - 全排列(一、二)
典型回溯题目 - 全排列(一、二) 46. 全排列 题目链接:46. 全排列状 题目大意: 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 注意:(1…...

数据清洗和特征选择
数据清洗和特征选择 数据清洗和特征挖掘的工作是在灰色框中框出的部分,即“数据清洗>特征,标注数据生成>模型学习>模型应用”中的前两个步骤。 灰色框中蓝色箭头对应的是离线处理部分。主要工作是 从原始数据,如文本、图像或者应…...

java StringBuilder 和 StringBuffer 万字详解(深度讲解)
StringBuffer类介绍和溯源StringBuffer类常用构造器和常用方法StringBuffer类 VS String类(重要)二者的本质区别(含内存图解)二者的相互转化StringBuilder类介绍和溯源StringBuilder类常用构造器和常用方法String类,St…...