JavaEE初阶Day 11:多线程(9)

目录

  • Day 11:多线程(9)
    • 生产者消费者模型
      • 1. 阻塞队列实现
    • 线程池
      • 1. 标准库线程池(ThreadPoolExecutor)
        • 1.1 corePoolSize & maximumPoolSize
        • 1.2 keepAliveTime & unit
        • 1.3 BlockingQueue<Runnable> workQueue
        • 1.4 ThreadFactory threadFactory
        • 1.5 RejectedExecutionHandler handler

Day 11:多线程(9)

生产者消费者模型

1. 阻塞队列实现

package thread;class MyBlockingQueue {private String[] elems = null;//[head, tail)//head位置指向的是第一个元素,tail指向的是最后一个元素的下一个元素private volatile int head = 0;private volatile int tail = 0;private volatile int size = 0;public MyBlockingQueue(int capacity){elems = new String[capacity];}void put(String elem) throws InterruptedException {synchronized (this) {while (size >= elems.length){//队列满了,进行队列阻塞this.wait();}//把新的元素放到tail所在的位置上elems[tail] = elem;tail++;if (tail >= elems.length) {//到达末尾,就回到开头tail = 0;}//更新size的值size++;//唤醒下面 take 阻塞的waitthis.notify();}}String take() throws InterruptedException {synchronized (this) {while (size == 0) {//队列空了,进行阻塞this.wait();}//取出 head 指向的元素String result = elems[head];head++;if (head >= elems.length) {head = 0;}size--;//take 成功一个元素,就唤醒上面put中的wait操作this.notify();return result;}}
}public class Demo31 {public static void main(String[] args) {MyBlockingQueue queue = new MyBlockingQueue(1000);Thread t1 = new Thread(() -> {try {int count = 1;while (true) {queue.put(count + "");System.out.println("生产" + count);count++;}} catch (InterruptedException e) {e.printStackTrace();}});Thread t2 = new Thread(() -> {try {while (true){String result = queue.take();System.out.println("消费" + result);Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}});t1.start();t2.start();}
}
  • wait不仅仅会被notify/notifyAll唤醒,也可能被其他的操作唤醒,比如interrupt
  • 上述代码加锁后,采用while本质上是当wait被唤醒之后,再次确认条件是否满足,是否能往下执行
  • 如果采用if进行条件判断,而不是while,那么一旦wait被唤醒了,此时逻辑就会往下走,不管条件判断是否成立,当前这个代码,理论上不会出现这个情况,即便是被interrupt唤醒,也是方法直接结束
  • 但是按照文档的建议,使用while的方法,是更稳妥的做法,被唤醒之后,再次确认一下,确认一下条件是否成立
  • 最好的做法就是只要使用wait的时候,都搭配while判定条件的方式

线程池

并发编程,使用多线程就可以了,线程比进程更加轻量,在频繁创建和销毁的时候,更有优势,但是随着时代的发展,对于“频繁”有个新的定义,现有的要求下,频繁创建销毁线程,开销也变得越来越明显了

如何进行优化

  • 线程池
  • 协程(纤程)

后续高版本Java中引入的虚拟线程,本质上就是协程;Go语言的主打卖点,就是使用协程处理并发编程

为什么引入线程池/协程之后能够提升效率,最关键的要点是

  • 直接创建/销毁线程,是需要用户态+内核态配合完成的工作,直接调用api创建线程和销毁线程,这个过程需要内核完成,内核完成工作很多时候是不太可控
  • 线程池/协程,创建和销毁,只通过用户态即可,不需要内核态的配合,使用线程池,提前把线程都创建好,放到用户态代码中写的数据结构里面,后面使用的时候,随时从池子里去取,用完了放回池子里去,这个过程完全是用户态代码,不需要和内核进行交互

简单来讲,就是一次性从系统申请出10个线程,可以用10个变量表示,也可以用一个长度为10的元素的数组来表示,也可以用hash等表示

协程本质上也是纯用户态操作,规避内核操作,不是在内核里把线程提前创建好,而是用一个内核的线程来表示多个协程(纯用户态,进行协程之间的调度)

常量池、数据库连接池、线程池、进程池、内存池的思想都是一样的

1. 标准库线程池(ThreadPoolExecutor)

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
1.1 corePoolSize & maximumPoolSize
  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数

标准库的线程池把线程分为两类

(1)核心线程:corePoolSize

(2)非核心线程:maximumPoolSize = 核心线程数 + 非核心线程数

动态扩展

  • 一个线程池,刚被创建出来的时候,里面就包含核心线程数这么多的线程
  • 假设此时线程池里包含4个线程,线程池会提供一个submit方法,往里面添加任务,每个任务都是一个Runnable对象
  • 如果当前添加的任务比较少,4个线程就足以能够处理,就只有4个线程在工作了
  • 如果添加的任务比较多,4个线程处理不过来,有很多的任务在排队等待执行,这个时候线程就会自动创建出新的线程,来支撑更多的任务
  • 创建出来的线程总数,不能超过最大线程数
  • 过了一段时间之后,任务没那么多了,线程清闲了,部分线程就会被释放掉(回收了),回收只是把非核心线程回收掉,至少保证线程池中线程数目不少于核心线程数

既可以保证任务多的时候的效率,也能保证任务少的时候,系统开销小

实际开发中,线程数应该设置成多少合适

不仅和电脑配置有关,更重要的是,和程序的实际特点有关系

极端一点,可以将程序分成两个大类

  • CPU密集型程序:代码完成的逻辑,都是要通过CPU来完成的,此时线程数目,不应该超过CPU逻辑核心数,例如代码逻辑都是在进行算数运算、条件判定、循环判定和函数调用
  • IO密集型程序:代码大部分时间在等待IO,等待IO是不消耗CPU的,不参与调度,不仅仅是标准输入输出,也可能是sleep、操作硬盘、操作网络等,此时瓶颈不在CPU上,每个线程消耗CPU只有一点点,更多的是考虑其他方面,比如网络程序,要考虑网卡带宽的瓶颈

上述两类都太理想化了,实际开发中,一个程序中既包含CPU操作,也包含IO操作,介于CPU密集和IO密集两者之间

最终的答案是:根据实验的方式,找到一个合适的值来设置线程数,对程序进行性能测试,测试过程中,设定不同的线程池数值,最终根据实际程序的响应速度和系统开销综合权衡,找到一个你觉得最合适的值

1.2 keepAliveTime & unit
  • keepAliveTime:非核心线程,允许空闲的最大时间,在线程池不忙的时候回收掉
  • unit:上述时间的单位,比如秒、分钟、小时、毫秒等

例如,设定保留时间3s,3s之内,非核心线程没有任务执行了,此时就可以回收了

1.3 BlockingQueue workQueue

线程池的任务队列,线程池会提供submit方法,让其他线程把任务提交给线程池

线程池内部需要有一个队列这样的数据结构,把要执行的任务保存起来,后续线程池内部的工作线程,就会消费这个队列,从而来完成具体的任务执行

1.4 ThreadFactory threadFactory

标准库中提供的用来创建线程的工厂类,这个线程工厂主要是为了批量的给要创建的线程设置一些属性,在工厂方法中,把线程的属性提前初始化好了

工厂模式也是一种设计模式,主要解决的是:基于构造方法创建对象存在的问题

例子:创建一个类来表示一个点

class Point {public Point(double x, double y) {...} //笛卡尔坐标系public Point(double r, double a) {...} //极坐标系
}

上述代码会出现编译报错,这两个版本的构造方法不能构成重载,此时无法通过构造方法来表示不同的构造点的方式了,本质上因为构造方法名字是固定的,无法搞成不同的方法名字,要想提供不同的版本,就只能想办法在参数上做出区分

工厂模式的核心思路是:不再使用构造方法创建对象,而是给构造方法包装一层

package thread;class Point {}class PointBuilder {public static Point makePointByXY(double x, double y) {Point p = new Point();p.setX(x);p.setY(y);return p;}public static Point makePointByRA(double r, double a) {Point p = new Point();p.setR(r);p.setA(a);return p;}
}public class Demo33 {public static void main(String[] args) {Point p = PointBuilder.makePointByXY(10, 20);}
}
1.5 RejectedExecutionHandler handler

面试官问:线程池的参数都是什么意思,本质是在考拒绝策略

拒绝策略:是一个枚举类型,采用哪种拒绝策略

例如:如果当前任务队列满了,仍然要继续添加任务怎么办,下列四种拒绝策略

  • ThreadPoolExecutor.AbortPolicy:直接抛出异常
  • ThreadPoolExecutor.CallerRunsPolicy:谁负责添加任务,谁负责执行,线程池本身不管了
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃掉最老的任务,让新的任务去队列中排队
  • ThreadPoolExecutor.DiscardPolicy:丢弃最新的任务,按照原有的节奏来执行

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

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

相关文章

windows docker desktop==spark环境搭建

编写文件docker-compose.yml version: 3services:spark-master:image: bde2020/spark-master:3.1.1-hadoop3.2container_name: spark-masterports:- "8080:8080"- "7077:7077"- "2220:22"volumes:- F:\spark-data\m1:/dataenvironment:- INIT_D…

Java框架 --- MyBatisPlus

一&#xff1a; MyBatisPlus 官方文档 MyBatis-Plus 二&#xff1a;

代码随想录算法训练营第四十四天| 完全背包,518. 零钱兑换 II ,377. 组合总和 Ⅳ

题目与题解 完全背包 题目链接&#xff1a;完全背包 代码随想录题解&#xff1a;​​​​​​​完全背包 视频讲解&#xff1a;带你学透完全背包问题&#xff01; 和 01背包有什么差别&#xff1f;遍历顺序上有什么讲究&#xff1f;_哔哩哔哩_bilibili 解题思路&#xff1a; 看…

云轴科技ZStack助力上银基金余额宝TA系统快速上线

上银基金管理有限公司&#xff08;上银基金&#xff09;通过ZStack Cloud云平台ZStack分布式存储融合架构构建关键余额宝TA系统&#xff08;开放式基金登记过户系统 &#xff09;实现业务快速如期上线。上银基金不仅可以借助ZStack云平台实现VMware纳管迁移&#xff0c;支持双机…

[C++11] 基础类型扩展解读(long long、char16_t char32_t)

说明&#xff1a; long long是一种基本数据类型&#xff0c;它通常是一种至少64位的有符号整数类型。在C中&#xff0c;long long类型能够存储的数值范围远远超过int或long类型&#xff0c;这使得它非常适合存储非常大的整数。 char16_t和char32_t是C11标准引入的两种新的数据…

【华为OD机试】高效货运【C卷|200分】

【华为OD机试】-真题 !!点这里&#xff01;&#xff01; 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 老李是货运公司承运人&#xff0c;老李的货车额定载货重量为 wt。 现有两种货物&#xff1a; 货物 A 单件重量为 wa&#xff0c;单件运费利润为 pa 货物 B 单件重量为…

Spring学习(三)——AOP

AOP是在不改原有代码的前提下对其进行增强 AOP(Aspect Oriented Programming)面向切面编程&#xff0c;在不惊动原始设计的基础上为其进行功能增强&#xff0c;前面咱们有技术就可以实现这样的功能即代理模式。Java设计模式——代理模式-CSDN博客 基础概念 连接点&#xff08…

2024经常用且免费的10个网盘对比,看看哪个比较好用!

网盘在我们的工作和学习中经常会用到&#xff0c;也是存储资料的必备工具&#xff0c;有了它&#xff0c;我们就不用走到哪都带着移动硬盘了&#xff0c;而目前市场上的主流网盘还有数十款&#xff0c;其中有免费的也有付费的&#xff0c;各家不一&#xff0c;今天小编就来为您…

Linux 安装 GHCup,GHC, cabal 以及通过 cabal 安装 pandoc

文章目录 安装 GHCUP1. 指定国内镜像2. 执行安装3. 安装检查 安装 pandoc1. 初始化包列表2. 安装命令3. Trouble ShootingFailure to build cryptonMemory Exhausted 内存不足 安装 GHCUP 1. 指定国内镜像 在 GHCup 官方网站 上面提供了安装方式&#xff1a; curl --proto h…

[Android]模拟器登录Google Play失败

问题&#xff1a; 模拟器登录Google Play失败&#xff0c;提示couldnt sign in there was a problem communicating with google servers. try again later. 原因&#xff1a; 原因是模拟器没有连接到互联网&#xff0c;打开模拟器中Google浏览器进行搜索一样不行。 解决&am…

移动硬盘(PSSD)中文件占用空间远大于文件大小

定义 文件的大小&#xff1a;文件内容实际具有的字节数&#xff0c;它以Byte为衡量单位&#xff0c;只要文件内容和格式不发生变化&#xff0c;文件大小就不会发生变化。 文件占用空间&#xff1a;文件在磁盘上的所占空间&#xff0c;它最小的计量单位是“簇(Cluster)”。 为…

C语言基础--数组和指针

数组和指针 数组与指针的关系与运用 在C语言中&#xff0c;数组和指针是两个重要的概念&#xff0c;它们之间有着密切的联系。本文将介绍如何通过指针操作数组元素&#xff0c;探讨指针数组的概念以及如何将数组名作为函数参数&#xff0c;帮助读者更好地理解和运用数组与指针…

MySQL高负载排查方法最佳实践(15/16)

高负载排查方法 CPU占用率过高问题排查 使用mpstat查看cpu使用情况。 # mpstat 是一款 CPU 性能指标实时展示工具 # 能展示每个 CPU 核的资源视情况&#xff0c;同时还能将资源使用情况进行汇总展示 # 如果CPU0 的 %idle 已经为 0 &#xff0c;说明此核已经非常繁忙# 打印所…

Istio介绍

1.什么是Istio Istio是一个开源的服务网格&#xff08;Service Mesh&#xff09;框架&#xff0c;它提供了一种简单的方式来为部署在Kubernetes等容器编排平台上的微服务应用添加网络功能。Istio的核心功能包括&#xff1a; 服务治理&#xff1a;Istio能够帮助管理服务之间的…

微服务之CircuitBreaker断路器

一、概述 1.1背景 在一个分布式系统中&#xff0c;每个服务都可能会调用其它的服务器&#xff0c;服务之间是相互调用相互依赖。假如微服务A调用微服务B和微服务C&#xff0c;微服务B和微服务C又调用其他的微服务。这就是构成所谓“扇出”。 如果扇出的链路上某个微服务的调…

状态压缩DP题单

P1433 吃奶酪&#xff08;最短路&#xff09; dp(i, s) 表示从 i 出发经过的点的记录为 s 的路线距离最小值 #include<bits/stdc.h> #define int long long using namespace std; const int N 20; signed main() { int n; cin >> n;vector<double>x(n 1),…

C++项目 -- 负载均衡OJ(三)online_judge

C项目 – 负载均衡OJ&#xff08;三&#xff09;online_judge 文章目录 C项目 -- 负载均衡OJ&#xff08;三&#xff09;online_judge一、基于MVC结构的oj服务设计1.结构与功能 二、oj_model.hpp1.建立文件版题库2.文件版题库的服务模块3. MySQL版题库3.1.创建名为oj_client的用…

关于提高自己技术能力的几点思考

关于提高自己技术能力的几点思考 回想自己进步快的时候&#xff0c;一个是读书时理解了书中逻辑&#xff0c;然后代入到工作能够解决问题&#xff0c; 一个是解决了一个个个新问题的时候总结出经验的时候。现在想想这两种方式确实是符合认知逻辑的&#xff0c;了解到知识&…

【uniapp】引入uni-ui组件库

&#xff08;1&#xff09;新建项目的时候选择 uni-ui项目 &#xff08;2&#xff09;已经创建好的项目去官网单独安装 跳转单独安装组件 https://uniapp.dcloud.net.cn/component/uniui/quickstart.html#%E9%80%9A%E8%BF%87-uni-modules-%E5%8D%95%E7%8B%AC%E5%AE%89%E8%A3%8…

【前端】修改iframe里面的pdf的样式

iframe是HTML中的一种元素&#xff0c;用于在网页中嵌入其他网页或文档。通过使用iframe&#xff0c;你可以在一个网页中显示另一个网页的内容。然而&#xff0c;由于安全性和隐私方面的考虑&#xff0c;通过CSS样式直接修改iframe中的内容是不被允许的。 但是&#xff0c;你可…