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

《javaEE篇》--线程池

线程池是什么

线程的诞生是因为进程创建和销毁的成本太大,但是也是相对而言,如果频繁的创建和销毁线程那么这个成本就不能忽略了。

一般有两种方法来进一步提高效率,一种是协程(这里不多做讨论),另一种就是线程池

假如说有一个学校食堂窗口的老板想到给学生提供外卖服务,而且有一个奇妙的想法,每当需要送外卖就现场雇一个学生来送,然后解雇。(就相当于平时的有一个任务就创建一个线程来处理),但是老板还是觉得频繁的雇佣和解雇学生的成本太大,于是老板又有一个点子。指定一个指标,外卖员的人数扩张到3个人,但还是随着外卖的数量逐步雇人。于是再有外卖来了老板就看,如果外卖员不足3人,就雇一个去送,若有3个人了,就先把外卖放到一边,等3个外卖员空闲时在送。这样的方法就类似于线程池。

 这样把线程创建好,放在“池子”里,后续用的时候直接从池子里取就好,不用系统进行创建,不用时还是放到池子里,不用系统销毁。那么为什么从池子取的效率就比创建新线程高?因为从池子取这个动作,是纯用户态的操作,而创建新的线程,这个动作则是,需要用户态+内核态互相配合。

线程池的优势

  • 线程池最大的好处就是减少每次启动、销毁线程的损耗。
  • 当有任务来时,不需要等待新线程的创建,利用已创建的线程就可以执行
  • 方便对线程进行统一管理和调度

工厂模式

线程池对象不是我们直接new的,而是专门通过一个方法,返回一个线程池对象,这种设计模式,就叫做工厂模式。

我们通常创建对象,使用new关键字。使用new就会触发构造方法,但是构造方法存在一定局限性。很多时候构造一个对象,希望有多种构造方式,多种方式就需要多个版本的构造方法来实现。但是构造方法要求方法的名字必须是类名,不同构造方法,就只能通过重载的方式来区分了。

 实践中,一般单独搞一个类,给这个类搞一些静态方法,由这些静态方法负责构造出对象

线程池的创建

  • 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
  • 返回值类型为 ExecutorService
  • 通过 ExecutorService.submit 可以注册一个任务到线程池中
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});

 Executors 创建线程池的几种方式

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.(随着往线程池里添加任务,这个线程池中的线程会根据需要自动被创建出来,创建出来之后也不会着急销毁,而是会在池子里保留一定时间,以备随时使用)
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.

上述几个工厂方法生成的线程池,本质上是 ThreadPoolExecutor 类的封装,这个类功能非常丰富,提供了很多参数,标准库上述的几个工厂方法,其实就是给这个类填写了不同的参数用来构造线程池。

(线程池的真正实现类是ThreadPoolExecutor).

线程池的参数

可见线程池有许多的参数具体如下:

corePoolSize(核心线程数):

线程池中会有一个最小的线程数量(核心线程数),即使这些线程处于空闲状态,也不会被销毁(除非设置了allowCoreThreadTimeOut为true)。当提交一个任务交给线程池后,线程池首先会检查当前线程数是否到达核心线程数,如果没有则创建一个新线程来处理这个任务。

maximumPoolSize(最大线程数):

如果当前线程数已经达到核心线程数,此时继续有任务添加,则会被缓存到工作队列中,如果队列也已经满了,就会创建一个新线程来处理这个任务(不是核心线程),但是线程不会无止尽的创建,最多创建的线程的数量就是有maximumPoolSize定的

keepAliveTime(线程限制超时时长):

如果一个线程处于空闲状态,并且当前线程数大于核心线程数(该空闲线程不是核心线程),那么在一定时间后该非核心线程将会被销毁(如果将allowCoreThreadTimeOut为true,一定时间后,空闲的核心线程也会被销毁)。这个时间就是keepAliveTime

unit(keepAliveTime的单位):

用来指定keepAliveTime的单位的,一般常用的单位有,TimeUnit.MILLISECONDS(毫秒)TimeUnit.SECONDS(秒)TimeUnit.MINUTES(分)。

workQueue(工作队列):

用来存放线程池中的任务的,可以根据需要灵活设置这里的队列是什么。需要优先级,就可以设置PriorityBlockingQueue,不需要优先级而且任务数目相对固定,可以使用ArrayBlockingQueue,如果不需要优先级,并且任务数目变动较大,可以使用LinkedBlockingQueue

threadFactory(线程工厂):

工厂模式的体现,使用threadFactory作为工厂类,由这个类负责创建线程,主要是为了,在创建线程过程中,对线程的属性做出一些修改。可以更方便的创建线程。

handler(线程池的拒绝策略):

一个线程池的任务容量达到上限,继续往线程池里添加任务的时候,会出现什么效果,JDK提供了4种策略

拒绝策略 

当线程池的线程数目达到最大线程数时,所执行的策略。 Executors给我们提供了四种常用的拒绝策略。

  1. ThreadPoolExecutor.AbortPolicy(默认):直接抛出RejectedExecutionException 异常
  2. ThreadPoolExecutor.CallerRunsPolicy:新添加的任务,由新添加任务的线程负责执行
  3. ThreadPoolExecutor.DiscarOldestPolicy:丢弃任务队列中最老的元素
  4. ThreadPoolExecutor.DiscarPolicy:丢弃当前新加的任务

线程数目 

在使用线程池时,需要设置线程数目,那么设置多少合适?N?N+1?2N?都不是。

一个线程执行的代码主要有两大类:

  1. cpu密集型:代码的主要逻辑是在进行算术运算/逻辑判断
  2. IO密集型:代码主要进行的是IO操作

假设一个线程的所有代码都是cpu密集型,这时线程池的线程数量不应该超过N,设置比N更大时,也无法提高效率了(cpu吃满了)此时更多的线程反而增加了调度的开销.

再假设一个线程的代码都是IO密集型。这时程序不吃cpu,设置的线程数就可以超过N 

所以代码不同线程池的线程数目设置就不同,就无法知道一个代码具体内容是cpu聚集多一些,还是IO聚集多以一些。

正确的做法应该是:使用实验的方式,对程序进行性能测试,实验过程中尝试修改不同的线程池线程数目,看看那种情况下最符合要求

实现简单线程池

class MyThreadPool{//任务队列private BlockingQueue<Runnable> queue= new ArrayBlockingQueue<>(1);//通过这个方法,把任务添加到队列中public void submit(Runnable runnable) throws InterruptedException {//此处的拒绝策略是阻塞等待queue.put(runnable);}public MyThreadPool(int num){//创建出n个线程负责执行上述任务for (int i = 0; i < num; i++) {Thread t = new Thread(() -> {//让这个线程从队列中消费任务try {while(!Thread.interrupted()){Runnable runnable = queue.take();runnable.run();}} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();}}
}
public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool(4);for (int i = 0; i < 100; i++) {int id = i;Thread.sleep(500);myThreadPool.submit(new Runnable() {@Overridepublic void run() {//这里不能使用i因为匿名内部类的变量捕获,类里要使用不变值System.out.println("id" + id);}});}}

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • C++ 适配器 priority_queue(优先级队列)
  • Aiseesoft Mac Video Converter Ultimate:高效多能的视频转换与编辑工具
  • 【教程】Leetcode 必知必会常用函数(C 语言版)
  • C# 时间日期运算
  • HarmonyOS NEXT 地图服务中‘我的位置’功能全解析
  • 基于机器学习的二手房房价数据分析与价格预测模型
  • 上传PDF、DOC文件到SAP HCM系统中案例
  • CSS文本样式(二)
  • Day16_Zookeeper
  • (152)时序收敛--->(02)时序收敛二
  • sql主从表的区分
  • 盘古信息IMS MCM制造协同管理系统:为中小企业数字化转型量身打造的数字化方案
  • Axure设计之下拉单选框教程(中继器)
  • 数据库不停机迁移方案
  • 【计算机组成原理】2.2.3_2 无符号数的加减运算
  • [iOS]Core Data浅析一 -- 启用Core Data
  • echarts的各种常用效果展示
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Fastjson的基本使用方法大全
  • HTTP--网络协议分层,http历史(二)
  • mockjs让前端开发独立于后端
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • PAT A1017 优先队列
  • python_bomb----数据类型总结
  • ReactNative开发常用的三方模块
  • Redis学习笔记 - pipline(流水线、管道)
  • Spring Boot MyBatis配置多种数据库
  • Vue ES6 Jade Scss Webpack Gulp
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 闭包--闭包之tab栏切换(四)
  • 程序员该如何有效的找工作?
  • 对象引论
  • 缓存与缓冲
  • 回顾2016
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • 数据结构java版之冒泡排序及优化
  • 消息队列系列二(IOT中消息队列的应用)
  • 正则与JS中的正则
  • 阿里云API、SDK和CLI应用实践方案
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • #100天计划# 2013年9月29日
  • #define、const、typedef的差别
  • (13)DroneCAN 适配器节点(一)
  • (Java入门)学生管理系统
  • (MTK)java文件添加简单接口并配置相应的SELinux avc 权限笔记2
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (八)c52学习之旅-中断实验
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (七)Knockout 创建自定义绑定
  • (三十)Flask之wtforms库【剖析源码上篇】
  • (转)用.Net的File控件上传文件的解决方案
  • (转)原始图像数据和PDF中的图像数据
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • .bat批处理(二):%0 %1——给批处理脚本传递参数