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

【多线程奇妙屋】 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 方法 */publicclassMyThreadextendsThread{@Overridepublicvoidrun(){while(true){System.out.println("MyThread的run线程 正在运行...");try{MyThread.sleep(10000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}publicstaticvoidmain(String[] args)throwsInterruptedException{MyThread myThread =newMyThread();// 创建一个线程 myThread.start();// 主方法while(true){System.out.println("main线程 正在运行...");MyThread.sleep(10000);}}}
在这里插入图片描述

<2>. 创建流程

  1. 首先创建一个先继承Thread 类, 并重写 run 方法
  2. main 方法实例化一个 MyThread 类
  3. 调用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 方法 * */classMyThread1implementsRunnable{@Overridepublicvoidrun(){while(true){System.out.println("Runnable 中run线程 正在运行...");try{MyThread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}publicstaticvoidmain(String[] args)throwsInterruptedException{Thread myThread1 =newThread(newMyThread1()); myThread1.start();// 接口while(true){System.out.println("main线程 正在运行");MyThread.sleep(1000);}}}
在这里插入图片描述

对于这种方式的实现

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

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

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

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

/** * * 方法四: Thread 的匿名内部类 * 在匿名内部类中重写 Run 方法 * */classMyThread3{publicstaticvoidmain(String[] args)throwsInterruptedException{Thread myThread3 =newThread(){@Overridepublicvoidrun(){while(true){System.out.println("匿名类 MyThread的run线程 正在运行...");try{MyThread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}};// 创建线程 myThread3.start();// 输出主线程while(true){System.out.println("main 线程正在运行...");MyThread.sleep(1000);}}}
在这里插入图片描述

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

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

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

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

/** * 方法三 : Runnable 的匿名内部类 * 在匿名内部类中 实现 Run 方法 * */classMyThread2{publicstaticvoidmain(String[] args){Thread myThread2 =newThread(newRunnable(){@Overridepublicvoidrun(){while(true){System.out.println("内部类Runnable 中 Run方法正在执行...");try{MyThread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}});// 创建线程 myThread2.start();while(true){System.out.println("主线程方法正在调用....");try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}}
在这里插入图片描述

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

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

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

/** * 方法五: 使用 lambda 表达式简化 匿名内部类 * */classMyThread4{publicstaticvoidmain(String[] args)throwsInterruptedException{Thread myThread4 =newThread(()->{while(true){System.out.println("lambda 匿名类 MyThread的run线程 正在运行...");try{MyThread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(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. 上述总共 五种创建线程 的方式, 都是蛮重要的 , 小伙伴们务必多操练多熟悉里面的 代码流程和逻辑 哦~

三. 线程终止

1. 线程终止的初识

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

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

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

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

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

2. Java线程终止的简易版

publicclassMyThread1{publicstaticboolean state =true;publicstaticvoidmain(String[] args)throwsInterruptedException{Thread t =newThread(()->{while(state){System.out.println("hello Thread1");try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}});// 创建线程 t.start();Thread.sleep(3000); state =false;}}
在这里插入图片描述

对于上述的流程主要是:

  1. 首先定义一个布尔变量, 用来确定线程的状态为 true线程正在运行, 为 false 线程结束
  2. 其次在主线程中修改 线程状态, 让线程结束。 注意这种结束的方式是让线程中的 任务都执行结束 了 , 当需要再次执行任务的时候才 置为 false 的 , 是一种比较温柔的做法。

鱼式疯言

补充细节

在这里插入图片描述

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

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

其实这种变量捕获是对于 lambda 表达式 来说的, 如果要在 lambda 表达式 中使用 同一作用域下的变量 , 这个变量是不可以被修改的。
也就是说在上面 的 state = false 出现了 修改 , 所以 lambda 中就会编译失败。
那么出现这种问题的比较好的解决方案就是把 这个变量定义为成员变量, 这样 两种的作用域就不相同 了, 也从另外一个角度来看,内部的方法调用外部的成员是没有问题的,天经地义的

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

classThread2{publicstaticvoidmain(String[] args){Thread t =newThread(()->{// 先获取当前线程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){thrownewRuntimeException(e);}// 修改线程状态 t.interrupt();}}
在这里插入图片描述

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

实例化Thread对象, 在 run方法中 使用 currentThread 方法 来获取当前线程
获取当前线程后 , 使用 isInterrupted判断当前线程的状态,自身返回 false 代表 线程正在进行, true 代表线程结束。
创建线程,并 休眠指定时间 , 使用 interrupted 来修改 isInterrupted 为 true, 让线程终止。
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的终止线程的方式是 比较温柔并且操作空间是很大 的。
如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正
希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖
在这里插入图片描述

Read more

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

大模型仍未对上商业的齿轮? 编译 | 王启隆 来源 | youtu.be/aWqfH0aSGKI 出品丨AI 科技大本营(ID:rgznai100) 现在的硅谷,空气里都飘着一股“再不上车就晚了”的焦躁感。 最近 OpenClaw 风头正旺,强势登顶 GitHub,终结了 React 神话,许多人更是觉得“AI 自己干活赚钱”的日子就在明天了。 特别是在斯坦福商学院(GSB)这种地方,台下坐着的都是成天琢磨怎么用下一个技术风口搞个独角兽出来的狠人。 微软的首席科学官(CSO)Eric Horvitz 被请到了这个几乎全美最想用 AI 变现的礼堂里。作为从上世纪 80 年代就开始搞 AI 的绝对老炮、也是微软技术底座的“扫地僧”,这位老哥并没有顺着台下的胃口,去吹捧下个月大模型又要颠覆什么行业,而是兜头给大家浇了一盆带点学术味的冷水。 他讲了一个挺有画面感的比喻:大家都在聊

By Ne0inhk
Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 当大模型能在几秒钟内生成一段“看起来像那么回事”的补丁时,开源社区却开始付出另一种代价。 最近,开源游戏引擎 Godot 的核心维护团队公开吐槽:他们正被大量“AI 生成的低质量代码”淹没。那些代码往往结构完整、注释齐全、描述洋洋洒洒,但真正的问题是——提交者可能并不理解自己交上来的内容。 这件事,并不是简单的“有人偷懒用 AI 写代码”。它正在触及开源协作最核心的东西:信任。 一场悄无声息的“AI 洪水” 事情的导火索来自一条 Bluesky 讨论帖。 Godot 主要维护者之一、同时也是 Godot 商业支持公司 W4 Games 联合创始人的 Rémi Verschelde 表示,所谓的“AI slop”

By Ne0inhk
诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

当宇宙级的“嘴炮”遇到降维打击。 编译 | 王启隆 来源 | youtu.be/l6ZcFa8pybE 出品丨AI 科技大本营(ID:rgznai100) 打开最新一期知名播客 StarTalk 的 YouTube 评论区,最高赞的一条留言是这样写的: “我长这么大,第一次看到尼尔·德葛司·泰森(Neil deGrasse Tyson)在一档节目里几乎全程闭嘴,像个手足无措的小学生一样乖乖听讲。” 作为全美最知名的天体物理学家,泰森平时的画风是充满激情、喋喋不休、用宇宙的宏大来震撼嘉宾。但这一次,坐在他对面的那位满头银发、带着温和英音的英国老人,仅仅用最平淡的语气,就让整个演播室陷入了数次令人窒息的沉默。 这位老人是 Geoffrey Hinton。深度学习三巨头之一,2024 年诺贝尔物理学奖得主,被公认为“AI 教父”。 对经常阅读 Hinton 演讲的我来说,这也是比较新奇的一幕—

By Ne0inhk
48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 「仅过了 48 小时,一笔 8.2 万美元的天价费用凭空出现,较这家小型初创公司的正常月费暴涨近 46000%。」 这不是假设的虚幻故事,而是一家墨西哥初创公司正在经历的真实危机。 近日,一位名为 RatonVaquero 的开发者在 Reddit 发帖求助称,由于他的 Gemini API 密钥被盗用,原本每月仅约 180 美元(约 1242 元)的费用,在短短 48 小时内暴涨到 82,314.44 美元(约 56.8 万元)。对于这家只有三名开发者的小型创业团队来说,这笔突如其来的账单,几乎等同于灭顶之灾。 “我现在整个人都处在震惊和恐慌之中。”RatonVaquero

By Ne0inhk