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

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

排序-冒泡排序(bubble sort)

冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;它重复地遍历待排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换&#xff0c;也就是说该数列已经排序完成…

[牛客网]——C语言刷题day2

答案&#xff1a;B 解析&#xff1a; char *p[10] 是指针数组,数组里存放了10个指针,在64位系统下指针占8个字节,所以sizeof(p) 10 * 8 80. char (*p1)[10]是数组指针,p1是一个指向存放10个char类型的数组的指针,所以sizeof(p1) 8. 答案&#xff1a;B 解析&#xff1a…

0513student的Maven项目

0513student的Maven项目包-CSDN博客 数据库字段 主页需求 点击休学按钮&#xff0c; 实现对 ‘’是否休学‘’ ‘’休学操作‘’ 的相应修改&#xff1b; 还有对数据库中相应学生休学状态修改。

11 | 如何实现高性能的异步网络传输?

理想的异步网络框架应该是什么样的? 这就是同步网络 IO 的模型。同步网络 IO 模型在处理少量连接的时候,是没有问题的。但是如果要同时处理非常多的连接,同步的网络 IO 模型就有点儿力不从心了。 因为,每个连接都需要阻塞一个线程来等待数据,大量的连接数就会需要相同数量…

vue3+TS或JS, 实现粒子特效 @tsparticles/vue3

在跟着B站视频BV11s4y1a71T学习时&#xff0c;使用到了粒子效果&#xff0c;但是以下这种情况只适用于项目是基于typescript的写法&#xff0c;否则无法实现。 粒子效果 VUE3TStsparticles/vue31、安装2、main.ts 引入3、App.vue4、效果 VUE3JS非最新版1、安装低版本的vue3-pa…

基于Java+SpringBoot+Vue前后端分离幼儿园管理系统设计与实现(有视频讲解)

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

git-删除workspace.xml的跟踪

问题描述 .gitignore 文件内容如下&#xff1a; .pyc *.pyc user_files/ .vscode/ __pycache__//.idea/misc.xml /.idea/modules.xml /.idea/inspectionProfiles/profiles_settings.xml /.idea/inspectionProfiles/Project_Default.xml /.idea/batrp_webbackend-server-dev.i…

申请免费的必应搜索API

申请免费的必应搜索API 文章目录 申请免费的必应搜索API前言一、原理1.1 登录1.2 进入1.3 获取密钥1.4 申请VISA信用卡1.5 创建必应自定义搜索资源 二、创建成功 前言 准备条件&#xff1a; 1、outlook邮箱 2、招商银行全币种VISA信用卡【建议之前就有一张招商银行信用卡&…

棒材直线度测量仪 专为圆形产品研发设计 在线无损检测

棒材直线度测量仪采用了先进的技术&#xff0c;能够实现在线无损检测&#xff0c;为生产过程提供了极大的便利。专为圆形产品设计&#xff0c;它能够精确测量棒材的米直线度及外径、椭圆度尺寸&#xff0c;为质量控制提供可靠的数据支持。 在线直线度测量仪不仅具有出色的性能…

Spring整合其他技术

文章目录 Spring整合mybatis思路分析Mybatis程序核心对象分析整合Mybatis 代码实现 Spring整合Junit修改成警告 Spring整合mybatis 思路分析 Mybatis程序核心对象分析 上面图片是mybatis的代码&#xff0c;上述有三个对象&#xff0c;分别是sqlSessionFactory&#xff0c;sqlS…

UVa11419 SAM I AM

UVa11419 SAM I AM 题目链接题意分析AC 代码 题目链接 UVA - 11419 SAM I AM 题意 给出一个 RC 大小的网格&#xff0c;网格上面放了一些目标。可以在网格外发射子弹&#xff0c;子弹会沿着垂直或者水平方向飞行&#xff0c;并且打掉飞行路径上的所有目标&#xff0c;如下图所…

Prosys OPC UA Simulation Server工程文件备份方法

Prosys OPC UA Simulation Server是一款免费的OPC UA服务器仿真软件&#xff0c;具体的使用和下载参考官网&#xff1a; Prosys OPC - OPC UA Simulation Server Downloads 他的免费版本不提供工程文件的备份、导入导出功能&#xff0c;每次退出时保存。如果需要工程备份&a…

灵卡 LCC262 高性能多功能数字视频和音频一体式采集卡详尽解读

一、前言 作为计算机视觉技术解决方案提供商——灵卡&#xff08;LingCard&#xff09;公司推出的新一代超群性能采集卡—— LCC262&#xff0c;以其卓越的性能表现和丰富多样的功能特性&#xff0c;为广大用户带来了前所未有的视频和音频处理体验。本文旨在对该产品进行详细解…

Vue 封装axios

【一】准备工作 &#xff08;1&#xff09;安装必要插件 安装Axios&#xff0c;这是必要的。默认最新版 npm install axios -S 或 cnpm install axios -S安装elementui-plus&#xff0c;用于提示信息 npm install element-plus --save # 或 cnpm install element-plus --s…

LangChain 核心模块学习 模型输入 Prompts

模型输入 Prompts 一个语言模型的提示是用户提供的一组指令或输入&#xff0c;用于引导模型的响应&#xff0c;帮助它理解上下文并生成相关和连贯的基于语言的输出&#xff0c;例如回答问题、完成句子或进行对话。 提示模板&#xff08;Prompt Templates&#xff09;&#xf…

洁太司检测试剂盒:肝癌早诊新利器,共筑健康未来

随着科技进步及医疗技术的不断创新&#xff0c;人类对疾病的早期诊断和治疗提出了更高的要求。 先思达生物近期推出的“洁太司-寡糖链检测试剂盒”&#xff0c;在原发性肝细胞癌的诊断领域实现了重大突破&#xff0c;获得了国家药品监督管理局&#xff08;NMPA&#xff09;的三…

JavaScript精粹(一)

JavaScript&#xff08;简称为JS&#xff09;是一种广泛应用于网页开发的脚本语言&#xff0c;具有以下几个主要作用&#xff1a; 网页交互&#xff1a;JavaScript 可以用于创建动态的网页效果&#xff0c;例如响应用户的操作&#xff0c;实现页面内容的动态更新&#xff0c;以…

JAVA云his医院管理系统源码(如何解决传统HIS的弊端、在医院管理中的具体应用场景有哪些)

开发技术&#xff1a; 前端&#xff1a;AngularNginx&#xff1b; 后台&#xff1a;JavaSpring&#xff0c;SpringBoot&#xff0c;SpringMVC&#xff0c;SpringSecurity&#xff0c;MyBatisPlus等&#xff1b; 数据库&#xff1a;MySQL MyCat&#xff1b; 基于云计算技术…

FreeRTOS事件标志组

目录 一、事件标志组的概念 1、事件标志位 2、事件标志组 二、事件标志组相关API 1、创建事件标志组 2、设置事件标志位 3、清除事件标志位 4、等待事件标志位 三、事件标志组实操 1、实验需求 2、CubeMX配置 3、代码实现 一、事件标志组的概念 1、事件标志位 表…

【Windows】回忆Win98

回忆Win98&#xff0c;又看到了这个Excel界面&#xff0c;上次还是十多年前的计算机课上 1、安装环境 Win11家庭版,23H2,VMware Workstation Pro 16 , 2、安装步骤及参考 虚拟机里的硬盘设置成SATA&#xff08;否则各种错误&#xff09;&#xff0c;安装MSDOS7.1&#xff…