首页 > Python资料 博客日记
【多线程奇妙屋】 Java 的 Thread类必会小技巧,教你如何用多种方式快速创建线程,学并发编程必备(实践篇)
2024-10-09 12:00:05Python资料围观29次
本篇会加入个人的所谓鱼式疯言
❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言
而是理解过并总结出来通俗易懂的大白话,
小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.
🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!
前言
想象一下,如果你的电脑只能一次执行一个任务,那会是多么的低效。幸运的是,Java提供了一种强大的机制,允许程序同时执行多个任务。这就是我们今天要探讨的主题——Java中的Thread类。
目录
-
Thread 类
-
创建线程的方式
-
线程终止
一. Thread 类
1. Thread 类的初识
对于线程的概念, 本身是 操作系统内核提出的概念 。
如果要执行并发编程, 就需要掌握不同的系统 api (例如 window 系统api , Linux 系统api ) 等… 这种 不同系统的api
是不一样的。
对于我们 Java程序猿 来说,
Java的api
早已分装好 对应的系统api , 我们这只需要学习Java的api
即可。
而进行并发编程的
Java最重要的api
就是 标准库中 Thread 类 。
通过这个类, 我们可以实现对于 线程的创建 , 以及利用每个线程进行
业务逻辑和任务
的执行。
鱼式疯言
并且
Thread
是java.lang
下面的一个库, 是 不需要手动导包 的。
二. 创建线程的方式
1. 创建线程
<1>. 代码展示
/**
* 创建线程:
* 继承 Thread
* 重写 run 方法
*/
public class MyThread extends Thread{
@Override
public 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>. 创建流程
-
首先创建一个先 继承Thread 类 , 并重写
run 方法
-
在
main 方法
中 实例化一个 MyThread 类 。 -
调用start() 方法来 创建线程 , 并执行run 方法里面的 业务逻辑。
鱼式疯言
补充总结:
由于我们在执行程序时, 系统就会分配资源, 自动创建进程 , 并且程序是需要 调度执行 , 所以
main 方法
自身就是一个线程:主线程
所以上述过程中, 可以认为 在 主线程 中又 创建了一个线程
。
<3>. 逻辑分析
run
中写着的是这个线程需要执行的 各种代码逻辑 , 相当于在main方法
中的 代码同等含义。
- 虽然在上述代码中 , 没有直接调用重写 的run 方法 , 但是当我们调用
start()
方法后, 在 Java代码中会重新调用我们重写的 run 方法 。
- 如果只是单纯的调用
run 方法
, 并 不能创建线程 。
- 对于上述过程有两个线程:
主线程
和myThread
, 都是分布在同一个系统资源下的, 所以需要并发执行
: 两个死循环都不会相互制约
, 各自执行各自的 。抢占执行
: 不能确定是哪个线程先执行到对应的逻辑。
鱼式疯言
补充细节
:
- 上诉代码中, 我们用到了 sleep() 方法,这个方法是 Thread 中的静态方法
(类方法)
。 可以让程序休眠一段时间
, 调用这个目的: 就是让程序猿好 观察程序的执行过程 。
() 内参数
指定的是多少毫秒(ms)
, 用于 指定休眠多少时间 , 其中1000 ms = 1s
换算进制。
- 调用sleep() 和 创建线程的过程 , 都是需要 抛出:
InterruptedException
这个异常的
2. 创建线程的其他方式
对于创建线程的方式:
除了上述 继承 Thread 重写run方法
之外还有其他常见四种方式
-
实现
Runnable
接口 , 重写 run 方法; -
使用 匿名内部类 , 对
Thread 类
重写 run 方法 ; -
使用 匿名内部类 , 实现
Runnable 接口
重写run 方法 ; -
使用 lambda 表达式 , 对
匿名内部类
进行简化 。
<1>. 实现 Runnable 接口
/**
* 方法二:
* 继承 Runnable
* 实现 run 方法
*
*/
class MyThread1 implements Runnable {
@Override
public 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 对象之前 :
- 首先 创建一个类 MyThread1 用于
实现 Runnable 接口
- 重写 run 方法
- 最终 new出对象 MyThread1 作为 Thread() 的参数 进行传入进行 实例化Thread 对象 即可。
后面的过程就和上面相同了, 小编在这里就不赘述了 💖💖🦊🦊🦊🦊
<2>. 匿名内部类—— 重写 Thread 的 run 方法
/**
*
* 方法四: Thread 的匿名内部类
* 在匿名内部类中重写 Run 方法
*
*/
class MyThread3 {
public static void main(String[] args) throws InterruptedException {
Thread myThread3 = new Thread(){
@Override
public 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() {
@Override
public 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 接口
的一种 简化版本 。
鱼式疯言
- 对于
lambda
表达的使用, 一定要注意一点的是: 必须是 函数式接口 , 也就是只有 一个抽象方法的接口才能使用 lambda 。
- 对于
start 创建线程
来说, 当调用 start 方法后, 系统内核会生成 PCB 并且添加链表, 创建新的线程。 所以当 多次调用 start 方法 时, 就会抛出异常, 因为对于一个Thread 对象来说, 只能start 一次, 也就是说只能创建一个线程
, 这个原因也是为了 JVM 方便管理 , 否则 一个Thread 对象对应多个线程 就会管理起来很复杂。
例如上述过程, 就会 抛出异常 : IllegalThreadStateException
- 上述总共 五种创建线程 的方式, 都是蛮重要的 , 小伙伴们务必多操练多熟悉里面的
代码流程和逻辑
哦~
三. 线程终止
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;
}
}
对于上述的流程主要是:
-
首先定义一个布尔变量, 用来确定 线程的状态 为
true
线程正在运行, 为 false 线程结束 。 -
其次在主线程中修改 线程状态, 让线程结束。 注意这种结束的方式是让线程中的
任务都执行结束
了 , 当需要再次执行任务的时候才 置为 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();
}
}
上述代码的流程主要分为 以下几步:
- 实例化Thread对象, 在 run方法中 使用
currentThread 方法
来 获取当前线程
- 获取当前线程后 , 使用
isInterrupted
判断当前线程的状态,自身返回false
代表 线程正在进行, true 代表 线程结束 。
- 创建线程,并
休眠指定时间
, 使用 interrupted 来修改isInterrupted
为 true, 让线程终止。
- 在
try catch{ }
在catch
中 添加 break , 直接跳出循环 。
4. 代码分析
对于上述代码小编还有很多话想和小伙伴们唠唠, 因为我个人觉得还是比较重要的, 小伙伴们可不要嫌我烦哦~ 🍖 🍖 🍖 🍖
- 如果 catch 中不添加 break ,而是按照平常的写法:
throw new RuntimeException(e);
直接抛出新的异常呢?
情况就会变成这样, 原因很简单, 对于线程执行的时间来说, 大部分时间是处于 sleep 的休眠状态 , 一旦有 外面的线程来修改线程的状态 ,
sleep
就会被打破休眠的状态
, 这时就会抛出异常
, 这时被 try catch { } 捕获到 , 就会执行throw new RuntimeException(e);
抛出新的异常, 由于这个 异常没有及时处理 ,编译器就会自动交给JVM 来处理
。 所以我们不需要在 catch { } 写throw new RuntimeException(e);
的方法。
- 居然写
throw new RuntimeException(e);
会抛出新的异常, 那么 catch 中什么都不写, break 也不写。 会发生什么呢?
如上图, 就会出现 即使我们使用 interrupted
来修改, 但是线程还是会继续的情况。
其实大家有所不知的是: 对于
休眠方法 sleep
是比较特殊的, 一旦被唤醒, 就会清除isInterrupted
修改后的 true 状态, 重新还原到 false 状态 , 这时线程就会 继续执行 了。所以这里如果我们要终止线程的话, 就需要 添加 break 来跳出 。
相比小伙伴们还是一头雾水吧 , 就算理解了也不知道它为啥要这样做吧 ? ? ? 🤔 🤔 🤔 🤔
下面削小编来举个栗子吧
假如有一天小编和女神去海边浪
此时小编这时坐在沙滩上打游戏
突然女神过来和说: 我口渴了, 你去买杯奶茶呗~
这时就凸显我以后的家庭地位了, 我就有三种选择:
-
我听到之后没有理会, 继续打我的游戏, 从中就凸显我的
“家庭帝位”
; -
我立马停下手中的游戏, 马上给女神买奶茶去 ,从中就凸显我的
“家庭弟位”
; -
我和她说: 等我这把游戏打完, 就给你去买, 从中就凸显我的
“家庭中位”
。
而上述的栗子从中也反映了: 终止线程过程中, 虽然
sleep
能够清除 isInterrupt 修改后的状态, 但是也为我们在 catch 中提供了 多方面的选择, 如果我们需要 跳出就break , 如果需要 继续执行就什么都不写 , 如果需要执行别的业务逻辑就添加进入即可
。
总结
-
Thread 类: 对于
Thread 类
而已是 Java封装 的一种 给 系统创建线程 的一个类的概念以及使用。 -
创建线程的方式: 掌握创建线程的主要流程和逻辑代码 , 以及熟悉这 五种线程创建的方式。
-
线程终止: 对于线程终止的两种方式: 强行终止的粗暴方式, 等待任务结束的温柔方式 。 Java的终止线程的方式是
比较温柔并且操作空间是很大
的。
如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正
希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖
标签:
相关文章
最新发布
- 【Python】selenium安装+Microsoft Edge驱动器下载配置流程
- Python 中自动打开网页并点击[自动化脚本],Selenium
- Anaconda基础使用
- 【Python】成功解决 TypeError: ‘<‘ not supported between instances of ‘str’ and ‘int’
- manim边学边做--三维的点和线
- CPython是最常用的Python解释器之一,也是Python官方实现。它是用C语言编写的,旨在提供一个高效且易于使用的Python解释器。
- Anaconda安装配置Jupyter(2024最新版)
- Python中读取Excel最快的几种方法!
- Python某城市美食商家爬虫数据可视化分析和推荐查询系统毕业设计论文开题报告
- 如何使用 Python 批量检测和转换 JSONL 文件编码为 UTF-8
点击排行
- 版本匹配指南:Numpy版本和Python版本的对应关系
- 版本匹配指南:PyTorch版本、torchvision 版本和Python版本的对应关系
- Python 可视化 web 神器:streamlit、Gradio、dash、nicegui;低代码 Python Web 框架:PyWebIO
- 相关性分析——Pearson相关系数+热力图(附data和Python完整代码)
- Python与PyTorch的版本对应
- Anaconda版本和Python版本对应关系(持续更新...)
- Python pyinstaller打包exe最完整教程
- Could not build wheels for llama-cpp-python, which is required to install pyproject.toml-based proj