当前位置: 首页 > news >正文

JavaEE之多线程

线程概念:

1)线程定义:线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际执行单元。每个线程都可以按照自己的顺序执行各自的代码,多个线程之间同时执行着多份代码。

2)为什么要有线程?:

单核cpu算力遇到瓶颈,需要多核cpu来提高算力。而并发编程可以解决这一问题。

其次,虽然多进程也可以实现并发编程,但是线程比进程更轻量,更具有优势:

1.创建线程比创建进程更快

2.销毁线程比销毁进程更快

3.调度线程比调度进程更快

所以大家通常会使用多线程

可以举一个形象的例子来形容多进程和多线程:

有一个房间,一张桌子,一个人和桌子上的一百只鸡,这个人的任务是将这一百只鸡吃完。

为了提高效率,引入多进程: 有两个房间,房间里分别有一个桌子,一个人和50只鸡,相比之前,效率会大幅度提升。

而多线程则是:一个房间,一张桌子,一百只鸡,两个人共同来完成这个任务,节省下了房间和桌子的开销,效率仍然能够大幅提高。

3.试想,如果引入更多的线程效率是否会进一步提高?

答案是否定的:

当线程数目太多,线程调度的开销也会进一步扩大,进而拖慢程序的性能。

4.在任务进行的过程中,如果有两个人同时看中了同一只鸡,就有可能产生“冲突”,导致线程不安全,有可能会使代码产生bug。当这两个人中的一个没能抢到鸡时,非常生气,就把桌子给掀了,我吃不了,大家都别吃了。

此时这个线程便会抛出异常(如果及时捕获处理掉,也不一定会导致进程终止),可能会带走整个进程,所有其他的线程都无法再继续工作。

3).进程和线程的区别:

1.进程包含线程,每个进程至少有一个线程存在,即主线程

2.进程与进程之间不共享内存空间,同一个进程的线程之间共享同一个内存空间。

3.进程是系统分配资源的最小单位,线程是系统调度的最小单位

4.一个进程挂了不会影响其他进程,但一个线程挂了,可能会把同进程内的其他线程一同带走(整个进程崩溃)

4)创建线程的四种写法:

4.11.继承Thread,重写run
  1. 首先定义一个类(这里我的类名为MyThread),这个类需要继承Thread
  2. 然后需要重写run方法,run方法内部就是我们要执行的线程代码
  3. 最后启动线程
class MyThread extends Thread{ @Override public void run(){ while(true){ System.out.println("hello Thread"); try { Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } } } public class Demo1 { public static void main(String[] args) throws InterruptedException { //创建Thread类的子类,在子类中重写Run方法 Thread t = new MyThread(); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }

在Thread父类中,本身有一个run方法,而我们可以重写这个方法,编写属于自己的逻辑

该代码中共有两个线程:

1.由newThread创建并调用t.start()启动,进入while循环后每秒打印“hello Thread”.

2.主线程(main):进入while循环每秒打印“hello main”

4.111sleep简单介绍:

sleep为休眠,意味着让当前线程放弃cpu,休息1000毫秒后再开始执行该任务。

注:

调用sleep会让当前线程处于阻塞状态(TIMED_WAITING),在此期间,其他线程会调用该线程的Interrupt()方法来中断它的休眠,被中断后,sleep便会抛出InterruptedException异常,从而让线程有机会响应中断请求。

而Java要求强制要求处理这个异常,通常在run方法中用try-catch或在方法签名中throws异常。

运行结果:

我们可以看到,执行过程中,有的时候main方法在前,有的时候Thread在前,

这是因为多个线程,调度顺序是随机的(操作系统内核控制),无法预测执行顺序,称为抢占式调度。

将t.start改为t.run后,由于主线程中并没有创建新的线程,而run又只是一个普通方法调用,主线程会直接执行run方法,进入while死循环打印hellothread。
4.112: 可以借助第三方工具来查看线程的具体执行情况:

上方的图标代表线程数量,左下角的代表当前进程中的线程。

4.12. 实现Runnable方法,重写run
  1. 定义一个类实现Runnable接口
  2. 重写run方法
  3. 构建Thread对象,将创建的Runnable对象作为参数传入
  4. 启动线程(t.start)
class MyRunnable implements Runnable { @Override public void run() { while (true) { System.out.println("hello Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo2 { public static void main(String[] args) throws InterruptedException { Runnable runnable = new MyRunnable(); Thread t = new Thread(runnable); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }

这两种方式推荐哪一个?

第二种,因为Runnable更加解耦合,方便后续代码的修改

耦合:两个代码的关联关系越大,耦合越大,推荐低耦合(后续代码如果出错,不会影响其他代码)

高内聚:将与某种逻辑关联的代码放到一块。(有条理),反之同理。

写代码的时候推荐低耦合高内聚。

4.13:使用Thread的匿名内部类:

1.创建一个Thread类的子类(匿名)

2.{}里编写子类的定义代码

3.创建这个匿名内部类的实例,将实例的引用赋值给t

public class Demo3 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }
4.14 使用Runnable的匿名内部类
public class Demo4 { public static void main(String[] args)throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } } }; Thread t = new Thread(runnable); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }
4.15:引入lambda表达式(推荐)
public class Demo5 { public static void main(String[] args)throws InterruptedException { Thread t = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch(InterruptedException e){ throw new RuntimeException(e); } } }); t.start(); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }

lambd表达式相当于是匿名内部类的替换写法,这种方法可以快速方便的就创建出一个线程

//lambda表达式本质上,是一个匿名函数(没有名字的函数,用一次就完了),主要来实现“回调函数”的效果

4.2 Thread类的其他属性和方法

前台线程与后台线程的区别:

前台线程:main线程及用户创建的线程,

后台线程:垃圾回收等辅助作用的线程(也叫做守护线程)

注:jvm会等待所有前台线程结束后,才会结束运行(不会等待后台进程)

前台线程好比一个酒桌中的话事人,而后台线程好比酒桌里的透明人,什么时候结束酒席由酒桌中的多个话事人决定。

4.21: 以下代码来形象的说明前台线程:

public class Demo6 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while (true) { System.out.println("hello 1"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); Thread t2 = new Thread(() -> { while (true) { System.out.println("hello 2"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t2.start(); Thread t3 = new Thread(() -> { while (true) { System.out.println("hello 3"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t3.start(); for (int i = 0; i < 3; i++) { System.out.println("hello main"); Thread.sleep(1000); } } }

虽然main线程结束了,但自己创建的3个前台线程还存在,所以进程还会存在,继续执行三个线程

4.22 IsAlive:判断线程是否存活:
public class Demo8 { public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } }); System.out.println(t1.isAlive()); //false 因为线程还没有创建 t1.start(); System.out.println(t1.isAlive()); while(true){ System.out.println("hello main"); Thread.sleep(1000); } } }

在t1线程还未创建之前,线程不存在,false,创建之后,为true(线程存在)。

4.23: setDaemon: 设置为后台进程。
public class Demo7 { public static void main(String[] args) throws InterruptedException{ Thread t = new Thread(()->{ while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ throw new RuntimeException(e); } } }); t.setDaemon(true); t.start(); for (int i = 0; i <3 ; i++) { System.out.println("hello main"); Thread.sleep(1000); } System.out.println("线程结束"); } }

将前台线程t设置为后台线程后,当main线程(前台线程)三次执行完后,后台进程也会随之关闭

4.24:中断线程interrupt:

常见的有两种方法来实现:

1.通过共享的标记来沟通、

2.调用interrupt方法来沟通

1.使用自定义的变量来做标志位:

定义一个当作线程中断标志的变量,通过其他线程对这个变量的修改,实现线程中断:

public class Demo10 { private static boolean isFinished = false; public static void main(String[] args) throws InterruptedException{ Test test = new Test(); Thread t = new Thread(()->{ while(!isFinished) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("thread 结束"); }); t.start(); Thread.sleep(3000); isFinished = true; } }

通过设置一个成员变量isFinished,在线程执行3s后更改成员变量isfinished的值,从而中断t线程。

lambda表达式会自动捕获方法内,之前出现的变量

lambda表达式内使用的标志,必须是final或者常量

2.使⽤ Thread.interrupted()或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位
public class Demo11 { public static void main(String[] args) throws InterruptedException{ Thread t = new Thread(()->{ System.out.println(Thread.currentThread().getName()); while(!Thread.currentThread().isInterrupted()){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch (InterruptedException e){ //throw new RuntimeException(e); break; } } System.out.println("t线程结束"); }); t.start(); Thread.sleep(3000); System.out.println("main线程尝试终止t线程" ); t.interrupt(); } }

Thread.currentThread(),静态方法,哪个线程调用,就获取到哪个线程的引用

isInterrupted,是为了判断线程是否被中断,thread变量里的Boolean值,中断为true,反之为false。

而interrupt方法会发送中断请求,修改Boolean变量里的值,还能唤醒像sleep这样的阻塞方法。

以上就是博主对线程知识的分享,在之后的博客中会陆续分享有关线程的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰

http://www.jsqmd.com/news/927686/

相关文章:

  • Python金融数据分析终极指南:5分钟掌握mootdx通达信接口实战
  • 避开建模‘深坑’:LCL滤波器参数对并网稳定性的影响到底该怎么分析?
  • stsb-xlm-r-multilingual优化策略:提升多语言语义理解性能
  • AI文档管理:从智能分类到自动化提取的7大核心优势
  • 不只是转图片:深入理解BraTs2020的.nii文件结构与Python可视化技巧
  • 从无人机到扫地机:手把手教你为不同移动平台配置ROS REP-105坐标系
  • Granite-3B-Code-Base-2K社区贡献指南:如何参与开源代码模型的发展
  • ALMA-13B-R参数配置详解:如何优化hidden_size与attention_heads提升翻译质量
  • 量子计算模块化架构中的耦合器布局优化技术
  • Instant-NGP 实战:用多分辨率哈希编码,5分钟让你的NeRF训练快100倍
  • 【教学类-160-43】20260524 AI视频培训-练习043“豆包AI视频《三字经》片段(演唱:04ZXY)+豆包图片风格:卡通
  • TRT-LLM深入理解之GPU基础/CTA/Kernel/Tile/算子/Cubin)
  • FOC 电流环PI 速度环PI
  • 数据预处理全流程解析:从EDA到特征工程的系统性方法
  • 一、Java程序的开发步骤
  • Snowflake Arctic-Embed-L OpenMind vs BGE-Large:谁才是检索任务的王者?
  • 如何永久保存微信聊天记录:WeChatMsg完整实战指南与深度解析
  • 基于边缘计算与Cloudflare Workers构建个人新闻聚合系统
  • TSL2591光传感器数据飘忽不定?可能是你的Arduino代码没调好增益和积分时间
  • M1/M2 MacBook 新手避坑指南:从JDK 1.8到MySQL 8.0,一次配好Java开发环境
  • 【Vue3 实战系列·第 02 篇】组件通信:Props·Emit·Provide/Inject·v-model——从父子到跨层级的通信全景
  • 别再只看容量了!手把手教你读懂电容Datasheet里的ESR、ESL和直流偏压曲线
  • 用C#和MQTTnet在WinForm里做个简易物联网监控后台(附完整源码)
  • 0–8岁英语启蒙书籍推荐(二)
  • InternLM2-7B-chat部署教程:MindSpore环境下的高效推理方案
  • 当AI学会了自己写代码:深入拆解OpenAI Codex CLI的Rust架构设计与工程哲学
  • 大模型多步推理提示工程实战:从思维链到自动化工作流
  • 避开LabVIEW打包陷阱:关于动态VI依赖(以报表工具包为例)的完整配置流程
  • 别再死记硬背了!用购物车和订单系统实战,5分钟搞懂UML类图的6种关系
  • LFM2.5-VL-450M WebGPU实时视频流字幕生成:浏览器端视觉AI应用的完整指南 [特殊字符]