【多线程奇妙屋】 Java 的 Thread类必会小技巧,教你如何用多种方式快速创建线程,学并发编程必备(实践篇)

本篇会加入个人的所谓鱼式疯言

❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言

而是理解过并总结出来通俗易懂的大白话,

小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.

🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!

在这里插入图片描述

前言

想象一下,如果你的电脑只能一次执行一个任务,那会是多么的低效。幸运的是,Java提供了一种强大的机制,允许程序同时执行多个任务。这就是我们今天要探讨的主题——Java中的Thread类。

目录

  1. Thread 类

  2. 创建线程的方式

  3. 线程终止

一. Thread 类

1. Thread 类的初识

对于线程的概念, 本身是 操作系统内核提出的概念

如果要执行并发编程, 就需要掌握不同的系统 api (例如 window 系统api , Linux 系统api ) 等… 这种 不同系统的api 是不一样的。

对于我们 Java程序猿 来说, Java的api 早已分装好 对应的系统api , 我们这只需要学习 Java的api 即可。

而进行并发编程的 Java最重要的api 就是 标准库中 Thread 类

通过这个类, 我们可以实现对于 线程的创建 , 以及利用每个线程进行 业务逻辑和任务 的执行。

鱼式疯言

并且 Threadjava.lang 下面的一个库, 是 不需要手动导包 的。

二. 创建线程的方式

1. 创建线程

<1>. 代码展示

/*** 创建线程:* 继承 Thread* 重写 run 方法*/public class MyThread extends Thread{@Overridepublic void run() {while (true) {System.out.println("MyThread的run线程 正在运行...");try {MyThread.sleep(10000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public static void main(String[] args) throws InterruptedException {MyThread myThread =new MyThread();// 创建一个线程myThread.start();// 主方法while (true) {System.out.println("main线程 正在运行...");MyThread.sleep(10000);}}
}

在这里插入图片描述

<2>. 创建流程

  1. 首先创建一个先 继承Thread 类 , 并重写 run 方法

  2. main 方法实例化一个 MyThread 类

  3. 调用start() 方法来 创建线程 , 并执行run 方法里面的 业务逻辑

鱼式疯言

补充总结

由于我们在执行程序时, 系统就会分配资源自动创建进程 , 并且程序是需要 调度执行 , 所以 main 方法自身就是一个线程: 主线程

所以上述过程中, 可以认为 在 主线程 中又 创建了一个线程

<3>. 逻辑分析

  1. run 中写着的是这个线程需要执行的 各种代码逻辑 , 相当于在 main方法 中的 代码同等含义
  1. 虽然在上述代码中 , 没有直接调用重写 的run 方法 , 但是当我们调用 start() 方法后, 在 Java代码中会重新调用我们重写的 run 方法
  1. 如果只是单纯的调用 run 方法 , 并 不能创建线程
  1. 对于上述过程有两个线程: 主线程myThread , 都是分布在同一个系统资源下的, 所以需要 并发执行 : 两个死循环都 不会相互制约各自执行各自的抢占执行: 不能确定是哪个线程先执行到对应的逻辑

鱼式疯言

补充细节

  1. 上诉代码中, 我们用到了 sleep() 方法,这个方法是 Thread 中的静态方法 (类方法) 。 可以让程序 休眠一段时间 , 调用这个目的: 就是让程序猿好 观察程序的执行过程

() 内参数 指定的是多少 毫秒(ms) , 用于 指定休眠多少时间 , 其中 1000 ms = 1s 换算进制。

  1. 调用sleep()创建线程的过程 , 都是需要 抛出: InterruptedException 这个异常的

2. 创建线程的其他方式

对于创建线程的方式:

除了上述 继承 Thread 重写run方法 之外还有其他常见四种方式

  • 实现 Runnable 接口 , 重写 run 方法;

  • 使用 匿名内部类 , 对 Thread 类 重写 run 方法

  • 使用 匿名内部类 , 实现 Runnable 接口 重写run 方法

  • 使用 lambda 表达式 , 对 匿名内部类 进行简化 。

<1>. 实现 Runnable 接口

/*** 方法二:* 继承 Runnable* 实现 run 方法**/class  MyThread1 implements  Runnable {@Overridepublic void run() {while (true) {System.out.println("Runnable 中run线程 正在运行...");try {MyThread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public static void main(String[] args) throws InterruptedException {Thread myThread1 = new Thread(new MyThread1());myThread1.start();// 接口while (true) {System.out.println("main线程 正在运行");MyThread.sleep(1000);}}
}

在这里插入图片描述

对于这种方式的实现

主要的流程还是在实例化Thread 对象之前 :

  1. 首先 创建一个类 MyThread1 用于 实现 Runnable 接口
  1. 重写 run 方法
  1. 最终 new出对象 MyThread1 作为 Thread() 的参数 进行传入进行 实例化Thread 对象 即可。

后面的过程就和上面相同了, 小编在这里就不赘述了 💖💖🦊🦊🦊🦊

<2>. 匿名内部类—— 重写 Thread 的 run 方法

/**** 方法四: Thread 的匿名内部类* 在匿名内部类中重写 Run 方法**/class  MyThread3 {public static void main(String[] args) throws InterruptedException {Thread myThread3 = new Thread(){@Overridepublic void run() {while (true) {System.out.println("匿名类 MyThread的run线程 正在运行...");try {MyThread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};// 创建线程myThread3.start();// 输出主线程while (true) {System.out.println("main 线程正在运行...");MyThread.sleep(1000);}}
}

在这里插入图片描述

我们知道, 对于 匿名内部类 来说, 是一种不带引用的一种对象, 也就是说当

实例化 Thread 类对象 时, 我们使用匿名内部类的方式就是在 Thread () 后面 加上 { } , 并在{ } 内部 重写 run 方法

以上就是唯一的区别 , 其他操作都一样。

<3>. 匿名内部类 —— 实现 Runnable 接口的 run 方法

/**
* 方法三 : Runnable 的匿名内部类
* 在匿名内部类中 实现 Run 方法
*
*/class  MyThread2 {public static void main(String[] args) {Thread myThread2 = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("内部类Runnable 中 Run方法正在执行...");try {MyThread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});// 创建线程myThread2.start();while (true) {System.out.println("主线程方法正在调用....");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

在这里插入图片描述

对于 匿名内部类 而已 , 使用的方式和 上面一种方式相同 , 而在这里唯一的区别就在于 , 上面 new 出了 new Runnable 作为参数 进行 传入到 Thread 对象中。

而本方式中 , 则是在 Thread 后面 直接 重写 run 方法

<4>. 使用 lambda 表达式 简化匿名内部类

/*** 方法五:  使用 lambda 表达式简化 匿名内部类**/class  MyThread4 {public static void main(String[] args) throws InterruptedException {Thread myThread4 = new Thread(()->{while (true) {System.out.println("lambda 匿名类 MyThread的run线程 正在运行...");try {MyThread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 创建线程myThread4.start();// 输出主线程while (true) {System.out.println("main 线程正在运行...");MyThread.sleep(1000);}}
}

在这里插入图片描述

对于 lambda 表达式 来说,

最核心的部分就是 :

new Runnable() {} 就是简化成 ()-> {} 来使用, 其实本质上还是通过 匿名内部类 实现 Runnable 接口 的一种 简化版本

鱼式疯言

  1. 对于 lambda 表达的使用, 一定要注意一点的是: 必须是 函数式接口 , 也就是只有 一个抽象方法的接口才能使用 lambda
  1. 对于 start 创建线程 来说, 当调用 start 方法后, 系统内核会生成 PCB 并且添加链表, 创建新的线程。 所以当 多次调用 start 方法 时, 就会抛出异常, 因为对于一个Thread 对象来说, 只能start 一次, 也就是说 只能创建一个线程 , 这个原因也是为了 JVM 方便管理 , 否则 一个Thread 对象对应多个线程 就会管理起来很复杂。

在这里插入图片描述

例如上述过程, 就会 抛出异常 : IllegalThreadStateException

  1. 上述总共 五种创建线程 的方式, 都是蛮重要的 , 小伙伴们务必多操练多熟悉里面的 代码流程和逻辑 哦~

三. 线程终止

1. 线程终止的初识

线程终止的方式有很多种终止的方式, 有粗暴的, 也有温柔的 。

像粗暴的 :让 执行到一半的线程 直接终止

像温柔的: 让 线程执行完整个任务 才终止

对于Java 来说, 我们是下面这种方式, 让 线程执行完所有的任务 才终止。

下面就让我们来瞧瞧呗 ❣️ ❣️ ❣️ ❣️

2. Java线程终止的简易版

public class MyThread1 {public static boolean state = true;public static void main(String[] args) throws InterruptedException {Thread t  = new Thread(() -> {while(state) {System.out.println("hello Thread1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//        创建线程t.start();Thread.sleep(3000);state = false;}
}

在这里插入图片描述

对于上述的流程主要是:

  1. 首先定义一个布尔变量, 用来确定 线程的状态true 线程正在运行, 为 false 线程结束

  2. 其次在主线程中修改 线程状态, 让线程结束。 注意这种结束的方式是让线程中的 任务都执行结束 了 , 当需要再次执行任务的时候才 置为 false 的 , 是一种 比较温柔 的做法。

鱼式疯言

补充细节

在这里插入图片描述

入上图, 如果把 布尔类型放在 main 方法的内部呢?

其实就会有问题, 这归咎于我们的Java语法中有个小知识点: 变量捕获

其实这种变量捕获是对于 lambda 表达式 来说的, 如果要在 lambda 表达式 中使用 同一作用域下的变量 , 这个变量是 不可以被修改的

也就是说在上面 的 state = false 出现了 修改 , 所以 lambda 中就会 编译失败

那么出现这种问题的比较好的解决方案就是把 这个变量定义为 成员变量 , 这样 两种的作用域就不相同 了, 也从另外一个角度来看, 内部的方法调用外部的成员 是没有问题的,天经地义的

3. Java 自身的线程终止方法

class Thread2 {public static void main(String[] args) {Thread t = new Thread(() -> {// 先获取当前线程Thread currentThread = Thread.currentThread();//            判断线程状态while(!currentThread.isInterrupted()) {System.out.println("hello thread1");try {Thread.sleep(1000);} catch (InterruptedException e) {break;}}});//        创建线程t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}//        修改线程状态t.interrupt();}
}

在这里插入图片描述

上述代码的流程主要分为 以下几步:

  1. 实例化Thread对象, 在 run方法中 使用 currentThread 方法获取当前线程
  1. 获取当前线程后 , 使用 isInterrupted 判断当前线程的状态,自身返回 false 代表 线程正在进行, true 代表 线程结束
  1. 创建线程,并 休眠指定时间 , 使用 interrupted 来修改 isInterrupted 为 true, 让线程终止。
  1. try catch{ }catch添加 break直接跳出循环

4. 代码分析

对于上述代码小编还有很多话想和小伙伴们唠唠, 因为我个人觉得还是比较重要的, 小伙伴们可不要嫌我烦哦~ 🍖 🍖 🍖 🍖

  1. 如果 catch 中不添加 break ,而是按照平常的写法: throw new RuntimeException(e); 直接抛出新的异常呢?

在这里插入图片描述

情况就会变成这样, 原因很简单, 对于线程执行的时间来说, 大部分时间是处于 sleep 的休眠状态 , 一旦有 外面的线程来修改线程的状态sleep 就会 被打破休眠的状态 , 这时就会 抛出异常, 这时被 try catch { } 捕获到 , 就会执行 throw new RuntimeException(e); 抛出新的异常, 由于这个 异常没有及时处理 ,编译器就会自动交给 JVM 来处理。 所以我们不需要在 catch { } 写 throw new RuntimeException(e);的方法。

  1. 居然写throw new RuntimeException(e); 会抛出新的异常, 那么 catch 中什么都不写, break 也不写。 会发生什么呢?

在这里插入图片描述

如上图, 就会出现 即使我们使用 interrupted 来修改, 但是线程还是会继续的情况。

其实大家有所不知的是: 对于 休眠方法 sleep 是比较特殊的, 一旦被唤醒, 就会清除 isInterrupted 修改后的 true 状态, 重新还原到 false 状态 , 这时线程就会 继续执行 了。所以这里如果我们要终止线程的话, 就需要 添加 break 来跳出

相比小伙伴们还是一头雾水吧 , 就算理解了也不知道它为啥要这样做吧 ? ? ? 🤔 🤔 🤔 🤔

下面削小编来举个栗子吧

假如有一天小编和女神去海边浪

此时小编这时坐在沙滩上打游戏

突然女神过来和说: 我口渴了, 你去买杯奶茶呗~

这时就凸显我以后的家庭地位了, 我就有三种选择:

  1. 我听到之后没有理会, 继续打我的游戏, 从中就凸显我的 “家庭帝位”

  2. 我立马停下手中的游戏, 马上给女神买奶茶去 ,从中就凸显我的 “家庭弟位”

  3. 我和她说: 等我这把游戏打完, 就给你去买, 从中就凸显我的 “家庭中位”

而上述的栗子从中也反映了: 终止线程过程中, 虽然 sleep 能够清除 isInterrupt 修改后的状态, 但是也为我们在 catch 中提供了 多方面的选择, 如果我们需要 跳出就break , 如果需要 继续执行就什么都不写 , 如果需要 执行别的业务逻辑就添加进入即可

总结

  1. Thread 类: 对于 Thread 类 而已是 Java封装 的一种 给 系统创建线程 的一个类的概念以及使用。

  2. 创建线程的方式: 掌握创建线程的主要流程和逻辑代码 , 以及熟悉这 五种线程创建的方式。

  3. 线程终止: 对于线程终止的两种方式: 强行终止的粗暴方式等待任务结束的温柔方式 。 Java的终止线程的方式是 比较温柔并且操作空间是很大 的。

如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正

希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA智慧社区系统跑腿家政本地生活商城系统小程序源码

智慧社区系统集成跑腿家政与本地生活商城 —— 打造便捷高效的社区生活圈 &#x1f3e0; 智慧社区新时代&#xff1a;一站式服务新体验 在快节奏的都市生活中&#xff0c;智慧社区系统正悄然改变着我们的生活方式。它不再只是一个居住的空间&#xff0c;而是集成了跑腿家政、本…

无线物联网通信与智能家居

无线物联网通信技术与智能家居之间存在着密不可分的关系。无线物联网通信技术作为智能家居系统的核心支撑&#xff0c;为智能家居设备之间的互联互通提供了可能&#xff0c;从而实现了家居生活的智能化、便捷化和舒适化。 一、无线物联网通信技术在智能家居中的应用 1、传感器…

【C++】vector详解:接口使用、迭代器、内存理解、与模拟实现

文章目录 1. 前言2. 内存角度 理解3. vector的使用定义 | 构造函数vector iteratorvector 空间增长问题vector 增删查改vector 迭代器失效避免迭代器失效的建议 4. 如何理解 二维动态vector5. 模拟实现 vector6. 相关文档 1. 前言 vector 是 C 标准模板库&#xff08;STL&…

万界星空科技数字孪生:解锁制造业未来,重塑智慧工厂新纪元

万界星空科技的数字孪生技术是一项创新的技术解决方案&#xff0c;它深度融合了工业大数据、物联网&#xff08;IoT&#xff09;、人工智能&#xff08;AI&#xff09;等先进技术&#xff0c;为制造业工厂提供了一个高度智能化、可视化的运营管理系统。以下是对万界星空科技数字…

《程序猿之Redis缓存实战 · 集合类型》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

「OC」探索 KVC 的基础与应用

「OC」KVC的初步学习 文章目录 「OC」KVC的初步学习前言介绍KVC的相关方法key和keyPath的区别KVC的工作原理KVO的setValue:forKey原理KVO的ValueforKey原理 在集合之中KVC的用法1. mutableArrayValueForKey: 和 mutableArrayValueForKeyPath:2. mutableSetValueForKey: 和 muta…

Java项目实战II基于Java+Spring Boot+MySQL的智能物流管理系统(文档+源码+数据库)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 随着电子商务的蓬勃发展&#xff0c;物流行业迎来了前所未有的挑战与机遇。传统物流管理方式在应对海…

PDCA优化任务流程

这里写目录标题 一、背景二、PDCA重要性与必要性概念PDCA循环的重要性 三、PDCA分析这次任务的执行任务描述分析原因&#xff1a;结合PDCA分析&#xff1a;提高办法&#xff1a; 四、总结 一、背景 汇报任务完成情况&#xff0c;未提交实际成果。 本次总结旨在通过PDCA循环的视…

二值图像的面积求取的两种方法及MATLAB实现

一、引言 面积在数字图像处理中经常用到&#xff0c;在MATLAB中&#xff0c;计算二值图像的面积通常可以通过两种主要方法实现&#xff1a;遍历法和直接利用bwarea函数。下面将分别介绍这两种方法的原理和相应的MATLAB代码示例。 二、遍历法计算二值图像面积的原理和MATLAB代码…

如何创建虚拟环境并实现目标检测及验证能否GPU加速

创建虚拟环境&#xff1a; 先创建一个虚拟python环境&#xff0c;敲如下代码 然后再到该虚拟环境里面安装自己想要的包 激活虚拟环境 然后再聚类训练这些 验证GPU加速 阿里源 pip install torch torchvision -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mir…

Appinventor2 多屏幕之间如何共享过程?

先说结论&#xff1a;不能共享&#xff0c;但可以变通&#xff0c;这个问题上没有完美方案&#xff01; Appinventor2 多屏幕之间如何共享过程&#xff1f;或者说如何跨屏幕调用其他屏幕的过程&#xff1f; 相信有很多人有过这样的问题&#xff0c;但是目前来看每个屏幕都是独…

Linux下驱动开发实例

驱动开发 驱动与硬件的分离 在传统的嵌入式系统开发中&#xff0c;硬件信息往往是直接硬编码在驱动代码中的。这样做的问题是&#xff0c;当硬件发生变化时&#xff0c;比如增加或更换设备&#xff0c;就需要修改驱动程序的代码&#xff0c;这会导致维护成本非常高。因此&…

机器学习周报(9.23-9.29)

文章目录 摘要Abstract1 自监督学习&#xff08;Self-Supervised Learning&#xff09;1.1 BERT1.1.1 Masking Input1.1.2 Next Sentence Prediction1.1.3 BERT的使用方式 1.2 Why does BERT work?1.3 Multi-lingual BERT 2 pytorch中tensor相关函数学习使用2.1 张量拼接与拆分…

4G模组SIM卡电路很简单,但也要注意这些坑

上次水SIM卡相关的文章&#xff0c;还是上一次&#xff1b; 上一篇文章里吹牛说&#xff0c;跟SIM卡相关的问题还有很多&#xff0c;目的是为下一篇文章埋下伏笔&#xff1b;伏笔埋是埋下了&#xff0c;但如果债老是不还&#xff0c;心里的石头就总悬着&#xff0c;搞不好老板…

MAC的几个常见的快捷方式

1.mac 查看图片好的方式 默认查看图片的方式无法直接切换上一张下一张 解决方法&#xff1a; 1.&#xff08;最好的方法&#xff09;选中图片直接按空格&#xff0c;进入快速预览图片 2.就是全部选中然后打开&#xff0c;但是说实话有点奇怪&#xff0c;而且很占内存 3.直接显示…

Linux 信号捕捉

我们知道信号的处理不是即时的&#xff0c;进程在合适的时机才会处理信号&#xff0c;而这个时机就比如从内核态返回用户态。 1. 用户态与内核态 在操作系统中&#xff0c;用户态&#xff08;User Mode&#xff09;和内核态&#xff08;Kernel Mode&#xff09;是两种不同的C…

安卓主板_MTK4G/5G音视频记录仪整机及方案定制

音视频记录仪方案&#xff0c;采用联发科MT6877平台八核2* A78 6* A55主频高达2.4GHz, 具有高能低耗特性&#xff0c;搭载Android 12.0智能操作系统&#xff0c;可选4GB32GB/6GB128GB内存&#xff0c;运行流畅。主板集成NFC、双摄像头、防抖以及多种无线数据连接&#xff0c;支…

如何在 Kubernetes 上部署和配置开源数据集成平台 Airbyte?

在 Kubernetes 上部署和配置 Airbyte 是一个复杂但非常有价值的过程&#xff0c;特别是对于需要强大数据集成和数据处理能力的企业或团队。Airbyte 是一个开源的数据集成平台&#xff0c;允许用户从各种来源提取数据并加载到目标存储中。其强大的插件系统支持多种数据源与目标&…

新能源汽车储充机器人:能源高效与智能调度

新能源汽车储充机器人&#xff1a;开启能源高效利用与智能调度的未来之门 随着全球能源危机的日益加剧和环境污染问题的不断恶化&#xff0c;新能源汽车成为了未来交通领域的重要发展方向。然而&#xff0c;新能源汽车的普及不仅需要解决电池技术的瓶颈&#xff0c;还需要构建一…

labview更换操作系统后打开原VI闪退

labview更换操作系统后打开原VI闪退 问题描述&#xff1a; Windows11由家庭版更换为专业版后&#xff0c;重新安装labview2021&#xff0c;打开原来的项目&#xff0c;项目管理器可以正常打开&#xff0c;但是打开VI却闪退&#xff0c;并报错如下 出现这种原因主要是labview在…