Java——多线程

一.多线程

1.什么是多线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程的实际运作单位

简单理解多线程就是应用软件中相互独立,可以同时运行的功能(也可以理解为人体内相互独立,但可以同时运行的器官⌓‿⌓)

我们平时常用的Main方法,就是主线程

2.多线程的作用

单线程运行时,比如我们要创建一个变量,程序是需要等待时间的。而使用多线程,程序可以在多个线程之间来回运行,充分利用等待的时间,从而提高CPU的利用效率

8fbbe4c556f54fc2b81c772714c4b246.png

二.多线程的三种实现方式

在Java API中对于Thread类的描述中给出了多线程的两种启动方式

⒈Thread类

线程是程序中的执行线程。Java虚拟机允许应用程序并发地运行多个执行线程

6bac42e8fcab42da94de354d34f65a8e.png

下面我们就来结合多线程的启动方式来了解Thread类

⒉多线程的第一种启动方式

将类声明为Thread的子类

该子类应重写Thread类的run方法

接下来可以分配并启动该子类的实例

f40dfae5e6e84a0ba6e621d5aa1bad3c.png

如图:我们创建两条线程并启用

66e6069a5bec43e2a5cde66eb6e5b51e.png

3d829dfe3fe24549bb15f753e46cf0e8.png

⑴此方法中的Thread类

①构造方法

Ⅰ.Thread()

Ⅱ. Thread(String name)

其中的参数传递的就是线程的名字,默认为Thread-序号

②常用方法

Ⅰ start

void start() 使该线程开始执行

start方法是Thread类的基础方法,有了它线程才能够启动执行,而它又不像流那样需要close关闭,当线程结束后它会自动关闭

Ⅱ getName

String getName() 返回该线程的名称

当线程我们没有手动命名,getName默认返回的是Thread-序号

Ⅲ sleep

20e30a243aa046d981217b33b552cf64.png

在多条线程执行时,当一条线程抢占到CPU执行权,它的执行时间是不确定的,那么它就可能一直占有着,当执行完毕后才能轮到下一个线程

04a653f4f5634a2582c700036068d055.png

比如上图,当一条线程执行完毕后才能轮到下一个线程

那我们想要线程轮流执行,那么就可以使用sleep让线程睡一会 

如图:当我们执行完打印语句后,就让线程睡1毫秒,将执行权让给另一条线程

7933a4c48f6142459f5b6d3c997eff1b.png

如图:看运行结果,两条线程就差不多是交替执行,而不是一条线程执行到底

6da30569c36245988b3a2e2c1916f814.png

Ⅳ  setPriority

void setPriority(int newPriority)  设置线程的优先级

void getPriority()  获取线程的优先级

在Java中多条线程的执行是随机的,线程的优先级分为10个等级(1--10),优先级高的获取到CPU执行权的概率就越高,Java默认优先级为5

如图:优先级高的抢占到执行权的概率越大,而不是一定是它先执行完,这是概率问题

827b803babf44429bf5e18c3d59b3684.png

Ⅴ  setDaemon

 void setDaemong(boolean on)  将该线程设置为守护线程

守护线程就是当其他非守护线程执行完毕后,守护线程就没有存在的必要了,就会陆续结束,该守护线程可以不执行完结束

如图:我们将线程1设置为守护线程,线程2为非守护线程,当线程2执行完毕时,线程2会陆续结束,可以不会执行完

fdf09fc78a2643be85045c4f0769a119.png

250e7b5bc7e343b6ad23d0336a614871.png 

举个例子,当我们在扣扣的聊天窗口传输数据,聊天窗口就是非守护线程,而传输数据的窗口就是守护线程,当我们把聊天窗口关闭时,数据传输窗口也会陆续关闭

Ⅵ  yield

static void yield()  暂停当前正在执行的线程对象,并执行其他线程

yield方法可以出让当前的CPU执行权,但出让之后该线程仍然可能抢占到CPU执行权

因此除了sleep方法,该方法也可以让线程的执行尽可能的均匀些

Ⅶ  join

f2b63552f4a1488f8f5b91e356be307f.png

join方法设置的线程,当该线程抢占到CPU执行权之后,在规定的等待时间内就会一直执行完毕后,才会让出CPU的执行权

⒊多线程的第二种启用方式

将类声明实现Runable接口

然后实现run方法

最后创建子类对象并传递给Thread对象

4decdde05b2c4a22aebcd606c93dab62.png

如图:我们创建子类对象并传递给线程执行

a810a944cf424e79927e6739d405d4cd.png

4698037a794a4ba186ccf0654a19b97c.png

⑴此方法中的Thread类

①构造方法

92750b45c4514c2dac4f1493afbf62a8.png

方法传递Runable接口的实现类对象 

又因为Runable接口是函数式接口,因此我们可以使用匿名内部类的方式实现

f0a8a9aef1b742a396ca635d27fca751.png

②常用方法

Ⅰ currentThread

static void currentThread()  获取当前线程对象

Ⅱ getName

Runable方法启动线程获取线程名字与Thread方法获取不同

在Runable实现类中我们实现的是Runable接口,该类与Thread是没有关系的,那么我们就不能像第一种启用方式那样直接getName获取到线程的名字了

那怎么解决呢?

我们可以利用currentThread方法获取到当前执行线程的对象,然后再调用getName方法获取到当前线程的名字

02b364d27d264ed59cd2a8fcf7b1d255.png

4.多线程的第三种启用方式

在前面的两种启用方式中,run方法是没有返回值的

因此我们就需要一种有返回值的启用多线程的方法

FutureTask类可以的get方法可以获取到线程方法中的结果,且FutureTast实现了Runable接口,可以在第二种方式的基础上启用线程

f5d69d3771ec4f6c9de394fb9eea7a76.png

而其中构造方法可以传递一个Callable接口的实现类对象

e1d75cc6e0734956a6d1d7e56b3139b4.png

而Callable接口只有一个方法call可以返回线程计算的结果

d0529f7729644a6b9d280ac5abb01419.png

因此第三种启用方式就是在第二种方式的基础上稍加修改

首先创建Callable实现类并重写call方法

然后创建FutureTesk对象接受Callable实现类运行的结果

最后创建Thread类对象并启动

dc17b68f954a49a68d5644e89bea05c9.png

ce295bd6ad95406a96c5dfed03c4d9e1.png 

5.三种线程启用方式的对比

对于第一种启用方式,操作简单,可以直接使用Thread类中的方法。但是正因为它继承了Thread类,它就不能再继承其他类了,因此它的扩展性较差

对于第二,三种启用方式,它没有继承任何类,因此它的扩展性就强些。但是它的编程相对的就复杂些,不能直接使用Thread类中的方法

第一,二种启用方式无法获取到方法返回值,因此就有了第三种方式可以获取到方法返回值

三.线程的安全问题

在多线程的执行中,若我们有一个共享的变量size在随着线程的执行变化着,因为线程的执行是随时随机抢占的,那么size就会有线程安全问题

如图所示例子:

8c2786d8231f4875a63ab56c543f930b.png

那么为了维护线程操作共享数据运行时的安全性问题,我们可以将这共享数据的代码用锁锁起来,当线程进入锁后,其他线程在外等候,当锁里面的代码执行完毕后,其他线程才能抢夺执行

1.同步代码块

格式:

synchronized(锁){操作的共享数据}

这里面的锁对象一定要是唯一的,只有相同的一把锁我们才可以让多条线程开锁解锁

这把锁可以是任意类型的共享对象

如:static Object o=new Object;

但是通常我们会使用本类的字节码文件

类名.class

如图:我们利用本类的字节码文件作为唯一的锁对象

e35f9b93837241ecaa07c29428a09151.png

2.同步方法

同步方法就是把synchronized关键字加到方法上,表示把这一个方法的所有代码给锁起来

格式:

修饰符 synchronized 返回值类型 方法名(方法参数){}

同步方法的锁对象我们不能自己指定,Java给我们指定好了锁对象

当是非静态方法时,锁对象是this,表示方法调用者

当是静态方式时,所对象是当前类的字节码文件

如图:在之前我们学习的StringBuffer中,我们同用可以看到同步方法的身影,这表示StringBuffer是线程安全的

611c00140ee8484c9d559364f08bed48.png

3. Lock锁

synchronized操作简单但是我们无法进行更多关于锁的操作,而Lock相比于synchronized可以进行更广泛的锁定操作,允许更灵活的结构,可以支持多个相关的Condition对象

⑴Lock类

如图: Lock类是一个接口,不能直接实例化,我们常用它的实现类ReentrantLock(可重入锁)来实例化

18ddadbc7b1f4c01be269b6861489f66.png

①成员方法

3f57a98e622946109a68ed3a3c49ac97.png

Ⅰ lock和unlock

lock开锁, unlock解锁,这两个方法是最基本的锁,同样的锁对象必须是唯一的

利用lock锁有一个小细节

如图:若我们直接lock与unlock,就会遇到下面的问题,有线程拿着钥匙跑了,其他线程结束不了!!!

1793469074e04509a2655ab32a7a03f3.png

因此我们需要一个解决办法,无论线程怎样执行,unlock必须执行。那么我们就可以使用try...finally来包裹unlock,让锁必须释放

19d4d7d5058e4b419d6de5ca898c9960.png

Ⅱ newCondition

lock锁通常会与Condition类结合使用来进行一些对于锁的操作

比如在阻塞队列中的使用

https://blog.csdn.net/m0_74808313/article/details/132196171

4.死锁问题

当锁嵌套时,通常会遇到死锁问题

如图:当线程1拿到了A锁等待B锁,而同时线程2拿到了B锁等待A锁,这时就导致了死锁

2cfa7efe2a1847489477efe8114fd5ad.png

因此为了防止死锁问题,我们好尽量减少锁的嵌套

四.多线程协作

多线程协作就是线程之间相互配合,共同完成某项工作

比如:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者是消费者

1.生产者和消费者

生产者和消费者模式是一个十分经典的多线程协作的模式,又叫做等待唤醒机制,打破了线程的随机机制,让多个线程轮流执行

所谓生产者消费者问题,实际上主要是包含了两类线程:

​ 一类是生产者线程用于生产数据

​ 一类是消费者线程用于消费数据

生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

2.等待唤醒机制的实现

⑴仓库,生产者和消费者的逻辑

首先我们需要一个仓库,当生产者生产物件后放入仓库,消费者消费物件拿出仓库

这个仓库需要有一把锁

当生产者进入时,若发现仓库中有物件,那么它就会沉睡等待,若发现仓库中没有物件,那么它就会生产物件并叫醒消费者

765cf1c6968640a4b80ae4ca7d65d170.png

当消费者进入时,若发现仓库中没有物件,那么它就会沉睡等待;若发现仓库中有物件,那么它就会消费物件并叫醒生产者

83c30897c80e4a1cb612d0b54ebcb3d4.png

⑵wait和notify

在Java的Object类中提供了相对应的方法来帮助我们解决线程的协作问题

d4833395a4a74d35903c82e865975ee0.png

d79d5bc661504b5da30f2f7b2d9851c1.png

注: wait和notify必须使用在同步方法或同步代码块内

⑶阻塞队列实现

首先我们需要一个阻塞队列,这个阻塞队列就代表仓库

然后我们完成生产者与消费者的逻辑

因为阻塞队列的put与take方法就是生产者与消费者的逻辑,因此我们在写生产者与消费者时就直接put,take,不用再进行逻辑的实现

af7e1868d9314b83a15b0e6bff146505.png

如图:我们写完生产者与消费者的逻辑,传递阻塞队列查看

37df77784e7440a094ef797112beae20.png

40e3152bb1424072943a435cd102f472.png

fae3ee76baa84c13b095ee98aad70d7a.png 

如图:查看打印语句发现生产者与消费者是轮流执行的,这样就实现了等待唤醒机制的逻辑

8f7769570f344cf7960da4dad72569e7.png

细节:因为锁是在put与take方法内部的,而打印语句在锁的外面,但并不影响共享数据的执行,只是不便于我们查看

五.线程状态

在给定的时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所以操作系统线程状态

73ca8c85b22d4bdbbf312adac17d8828.png

六.线程池

在以前我们写的多线程有弊端

当我们需要线程时就创建(NEW),当它运行完后就消失(TERMINAED),这样的话浪费操作系统的资源

因此我们需要线程池来优化

1.线程池核心原理

我们需要一个容器,当我们提交线程任务时,容器会创建新的线程对象,任务执行完毕,线程存入到容器,到下次直接拿出使用

若提交任务时容器中没有空闲线程且容器满了,那么其他线程排队等待

2.线程池实现

static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池

static ExecutorService newFixedThreadPool(int nThreads) 创建一个有上限的线程池

如图:我们创建一个大小为3的线程池,将前三个任务提交上去

6f7c491f282a4bf7af97f7f8e8e96454.png

可以看到当前排队的线程为0,工作中的线程为3,而当我们要提交下面的任务时,当前面的线程没结束它就会排队等待,只有当前面的线程运行完毕他们才能工作

73f8b0002b5c4970af5c1517a898f162.png

3.自定义线程池

当我们查看newFixedThreadPool时,可以看到它的底层是创建了一个ThreadPoolExecutor对象

b919557680d9410ba9b9797ec938adc8.png

ThreadPoolExecutor才是真正的线程池对象,它相比于前面的线程池来说更加灵活

⑴ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize ,  int maximumPoolSize ,  long keepAliveTime ,  TimeUnit unit , BlockingQueue<Runnable> workQueue ,  ThreadFactory threadFactory ,  RejectedExecutionHandler handler)  用给定的初始参数创建线程池

其中共有7个参数

参数一:核心线程数

参数二:最大线程数量

参数三:等待的空闲时间

参数四:时间单位

参数五:任务队列

参数六:创建线程工厂

参数七:要执行的任务过多时的解决方案

其中当我们提交的线程多于核心线程,多出的线程会等待

若线程数量超出最大线程数,那么会创建临时线程(最大线程数-核心线程数),让多出的线程工作

若线程数量超出最大线程数+临时线程,那么会对超出的线程进行处理

6afe4488384c49e0b8b4ca54a38d6c78.png

其中的处理方式有四种,分别被定义为内部类

311b241d720b434ea1a5dc9ab1a13212.png

⑵线程池实现

线程池的大小并不是我们随意规定的,而是需要通过公式计算出来的

①CPU密集型运算

当我们的项目中计算多而读取文件少,就要此方式类计算

最大并行数+1

最大并行数与我们电脑CPU的型号相关,因为操作系统不会把所有的线程给同一个软件,因此我们通常利用Java虚拟机来计算最大并行数

408ae63b49d6468f8c21190eaaa12702.png

②I/O密集型运算

当我们的项目计算少,读取数据多,那么就使用此类方式计算

最大并行数×期望CPU利用率×(总时间/CPU计算时间)

③代码书写

90b3cf11352d4889a4da588327d801fd.png

 

 

 

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

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

相关文章

如何用时尚新姿讲好中国品牌故事?

品牌建设在推动高质量发展中扮演了双重角色&#xff0c;既是高质量发展的重要“承载者”&#xff0c;也是强有力的“助推器”。5月10日-11日&#xff0c;中国时尚品牌URBAN REVIVO&#xff08;以下简称UR&#xff09;以中国品牌日为起点&#xff0c;联合天猫超级品牌日&#xf…

Linux提权--SUDO(CVE-2021-3156)Polkit(CVE-2021-4034)

免责声明:本文仅做技术学习与交流... 目录 SUDO(CVE-2021-3156) 影响版本 -判断&#xff1a; -利用&#xff1a; Polkit(CVE-2021-4034&#xff09; ​ -判断&#xff1a; -利用: 添加用户 SUDO(CVE-2021-3156) another: SUDO权限配置不当. 影响版本 由系统的内核和发…

Redis 5.0 Stream数据结构深入分析

Redis 5.0 Stream数据结构深入分析 目录 Redis 5.0 Stream数据结构深入分析 一、Stream数据结构概述 二、核心概念解析 三、Stream的特性与用途 四、案例研究&#xff1a;实时消息系统 五、性能优化与最佳实践 六、总结与展望 一、Stream数据结构概述 Redis Stream是Red…

FastAPI - Tortoise ORM 数据库基础操作

文章目录 1. 安装 Tortoise ORM2. 定义模型3. 初始化数据库连接4. 数据库操作4.1 创建数据4.2 查询数据4.3 更新数据4.4 删除数据 5. 使用 Pydantic 模型6. 关闭数据库连接7. fields类相关操作1. fields.IntField2. fields.BigIntField3. fields.SmallIntField4. fields.CharFi…

【LAMMPS学习】八、基础知识(6.3)使用 LAMMPS GUI

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语,以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各种模拟。 …

西门子博途WINCC动画之旋转运动

概述 本例将介绍在西门子 TIA Portal HMI 中旋转运动动画的一种实现方法。本例以风机、搅拌器和传送带为例&#xff0c;按下启动按钮开始转动&#xff0c;按下停止按钮停止转动。 第1步&#xff1a; 添加 PLC 设备。​博途TIA/WINCC社区VX群 ​博途TIA/WINCC社区VX群 选择西…

PyQt5中的QGraphicsView()

文章目录 1. 简介2. 一个简单的示例2. 加载一幅图片3. 常用方法示例 1. 简介 QGraphicsView是PyQt5中用于显示图形场景的小部件&#xff0c;它提供了许多常用的方法来控制视图的行为和属性。下面是一些常用的QGraphicsView方法&#xff1a; setScene(scene): 设置要显示的场景…

从零开始写 Docker(十四)---重构:实现容器间 rootfs 隔离

本文为从零开始写 Docker 系列第十四篇&#xff0c;实现容器间的 rootfs 隔离&#xff0c;使得多个容器间互不影响。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; 核心原理&#xff1a;…

SpringCloud 集成 RocketMQ 及配置解析

文章目录 前言一、SpringCloud 集成 RocketMQ1. pom 依赖2. yml 配置3. 操作实体4. 生产消息4.1. 自动发送消息4.2. 手动发送消息 5. 消费消息 二、配置解析1. spring.cloud.stream.function.definition 前言 定义 Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力…

spacy微调BERT-NER模型

数据准备 加载数据集 from tqdm.notebook import tqdm import osdataset [] with open(train_file, r) as file:for line in tqdm(file.readlines()):data json.loads(line.strip())dataset.append(data)你可以按照 CLUENER 的格式准备训练数据&#xff0c; 例如&#xff1…

(done) Beam search

参考视频1&#xff1a;https://www.bilibili.com/video/BV1Gs421N7S1/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 &#xff08;beam search 视频&#xff09; 参考博客1&#xff1a;https://jasonhhao.github.io/2020/06/19/…

在word中创建宏来多级列表的编号不显示的bug

出现问题的示意图如下&#xff0c;可以看出标题前面1.1消失了 第一步&#xff1a;选择开发工具 第二步&#xff1a; 第三步&#xff1a;选择当前文件&#xff08;创建宏后&#xff0c;方便查找&#xff09; 第四步&#xff1a; 第五步&#xff1a;打卡VB 第七步&#xf…

ONVIF系列一:ONVIF介绍

感谢博主OceanStar的学习笔记&#xff0c;ONVIF系列二和系列三中安装操作过程及代码实现参考了这位博主的博客。 ONVIF系列&#xff1a; ONVIF系列一&#xff1a;ONVIF介绍 ONVIF系列二&#xff1a;Ubuntu安装gSOAP、生成ONVIF代码框架 ONVIF系列三&#xff1a;ONVIF客户端实现…

机器人开发项目实现过程

比赛项目实现过程 第一步&#xff1a;设置远程桌面连接 登录机器人系统&#xff0c;设置网络&#xff0c;参考远程桌面连接20230525.mp4 外接显示器、鼠标和键盘 登录系统 账户&#xff1a;robuster 密码&#xff1a;123456 建议&#xff0c;手机开热点&#xff0c;机器人…

消费新纪元:探索消费增值的财富之旅

你是否曾对日常消费感到一丝无奈&#xff0c;觉得钱一旦花出去就如同流水般逝去&#xff0c;再也无法追回&#xff1f;现在&#xff0c;让我为你揭示一种革命性的消费观念——消费增值&#xff0c;它不仅能满足你的物质需求&#xff0c;还能让你的资金像滚雪球般持续增长&#…

鸿蒙ArkUI开发:常用布局【弹性布局方向图】

弹性布局方向图 Flex({ direction: FlexDirection.Row }) FlexDirection.Row&#xff08;默认值&#xff09;&#xff1a;主轴为水平方向&#xff0c;子组件从起始端沿着水平方向开始排布FlexDirection.RowReverse&#xff1a;主轴为水平方向&#xff0c;子组件从终点端沿着F…

关于我个人的编码规范(C/C++)

文章目录 前言一、文件结构1. 版权和版本声明&#xff08;不是必须&#xff0c;但是我建议看看&#xff09;2. 头文件结构3. 源文件结构 二、排版&#xff08;以 K&R 风格为主&#xff09;1. 缩进与左花括号的位置2. 空行的插入3. 该分行就分行4. 花括号5. 长语句分段6. 空…

vscode 实现本地服务器部署小结

在查阅 MDN 网站的时候&#xff0c;偶然发现的原来 vscode 也可以实现本地化服务器部署&#xff0c;来模拟服务器的运行。 安装插件 在VSCode的插件市场搜索并安装以下插件&#xff1a; – Live Server&#xff08;用于开启本地服务器&#xff09; – Debugger for Chrome&a…

【WB】微博爬虫案例_无头浏览器采集_selenium/playwright/requests方式采集

人立晚风月照中 独散步长廊 月浸在池塘 欢欣充满了心上 静听乐悠扬 越觉乐洋洋 夜鸟高枝齐和唱 月照彩云上 熏风轻掠 如入山荫心向往 &#x1f3b5; 苏妙玲《彩云追月》 import timeimport requests from playwright._impl._errors import TimeoutError f…

算法设计与分析(超详解!) 第三节 贪婪算法

1.贪心算法基础 1.贪心算法的基本思想 贪心算法是从问题的某一个初始解出发&#xff0c;向给定的目标推进。但它与普通递推求解过程不同的是&#xff0c;其推动的每一步不是依据某一固定的递推式&#xff0c;而是做一个当时看似最佳的贪心选择&#xff0c;不断地将问题实例归…