线程池原理及改造
目录
一 线程池执行原理
二 线程池改造(一)
三 线程池改造(二)
一 线程池执行原理
首先我们先了解一下线程池里面几个参数:

第一个是核心线程数,第二个是线程池最大线程数。(线程池里面的线程分为核心线程和非核心线程,既然核心线程有3个,那么说明还可以创建最多2个非核心线程)
第三个是最大空闲时间,第四个是时间单位为秒,
第五个是阻塞队列(这个很重要),第六个是线程工厂,默认的,
最后一个是拒绝策略。
知道了每一个参数后,我们现在就来解释线程池的工作原理:

首先,当任务进入线程池后(这里假设线程池是正常运行状态,所以这个就不再做判断),线程池会先判断任务数量是否大于核心线程池的数量,如果不大于,那么就直接创建核心线程来执行任务,但是如果任务数量大于核心线程池数量,比如你只有三个核心线程池,但是你有四个任务,那么前三个任务会创建核心线程池去执行,而多出来的那个任务会放到阻塞队列里面,等核心线程空闲了再去执行阻塞队列里面的任务。
但是如果现在来了五个任务,那么,前三个交给核心线程池,第四个放在阻塞队列里面等待,而我们设置的阻塞队列只有一个,剩下的那个任务就会创建一个非核心线程去执行这个任务。
如果现在来了七个任务,前三个交给核心线程,第四个放在阻塞队列,第五和第六个会创建非核心线程执行,那么第七个呢?现在非核心线程也已经满了。那么这个时候就会有参数里面的拒绝策略去拒绝这个任务,抛异常处理等等。。。
二 线程池改造(一)
以上就是整个线程池执行原理,然后我们发现这里就会有个问题。就是当阻塞队列没满的时候,任务会等待核心线程执行完才会被执行,那如果核心线程执行的任务非常耗时那不是阻塞队列里面的任务就会一直等。
所以我们现在第一个改造,就是如果核心线程池占用完了,新的任务不是直接去阻塞队列等待,而是直接创建非核心线程去执行任务。
怎么改呢?
我们先看看ThreadPoolExecutor类里面的execute方法的代码:
public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);}

从上面的源码可以看见,在第三步的时候,它的判断如果为true,就会把任务放进阻塞队列,然后第四步才是创建非核心线程执行任务。
我们现在是想要先创建非核心线程直接执行任务,而不是先让任务进入阻塞队列,那么我们可以在第三步的判断条件中,然后的条件为false,那么它就不会进入阻塞队列,而是直接跳到下一步,创建非核心线程去执行任务。
那么我们只能重写workQueue.offer()方法了。
我们继承阻塞队列这个类,然后重写里面的offer方法,直接让他返回false:
package com.feisi.threadPool;import java.sql.Array;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;public class TaskQueue<R extends Runnable> extends ArrayBlockingQueue<Runnable> {public TaskQueue(int capacity) {super(capacity);}public TaskQueue(int capacity, boolean fair) {super(capacity, fair);}public TaskQueue(int capacity, boolean fair, Collection<? extends Runnable> c) {super(capacity, fair, c);}@Overridepublic boolean offer(Runnable runnable) {
// 重写方法,直接返回falsereturn false ; }
}
然后我们把线程池的阻塞队列换成我们自己的:

看运行结果:

原先四个任务,前面三个有三个核心线程执行,后面一个在阻塞队列等待,直到核心线程执行完再去执行它,但是现在不一样了,任务4直接由非核心线程4去执行了。
三 线程池改造(二)
上面这个问题是解决了,我们加快了任务的执行速度,无需去等待核心线程执行完毕,就可以直接创建非核心线程去执行任务。
但是现在有一个问题,就是你直接返回false,相当于来一个任务,只要没超出最大线程数限制,你就直接给他创建一个非核心线程去执行,这样线程数多了容易影响性能,比如你来了四个任务,前三个由核心线程去执行,而第四个你就直接创建非核心线程了,那如果前面的核心线程已经执行完了呢?那是不是可以不需要创建非核心线程了,直接由空闲的核心线程去执行就行了,这样就可以大大减少线程的创建了。
所以我们应该再做一个判断,如果任务总数小于核心线程数,那么就由核心线程执行,如果大于核心线程数,那么就创建新的非核心线程去执行。
package com.feisi.threadPool;import java.sql.Array;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;public class MyTaskQueue<R extends Runnable> extends ArrayBlockingQueue<Runnable> {private EagerThreadPoolExecutor threadPoolExecutor;public MyTaskQueue(int capacity) {super(capacity);}public MyTaskQueue(int capacity, boolean fair) {super(capacity, fair);}public MyTaskQueue(int capacity, boolean fair, Collection<? extends Runnable> c) {super(capacity, fair, c);}public void setThreadPoolExecutor(EagerThreadPoolExecutor threadPoolExecutor) {this.threadPoolExecutor = threadPoolExecutor;}@Overridepublic boolean offer(Runnable runnable) {//1.获取当前线程池的线程数量int currentPoolSize = threadPoolExecutor.getPoolSize();//如果当前任务数量小于核心线程数, 让核心线程数来执行// 有核心线程在空闲if(threadPoolExecutor.getSubmittedTaskCount() < threadPoolExecutor.getCorePoolSize()){
// System.out.println("111");//让核心线程数来执行return super.offer(runnable);}//如果当前线程池线程数量 < 最大线程池而且说明核心线程没有空闲那么就创建非核心线程执行。if(currentPoolSize < threadPoolExecutor.getMaximumPoolSize() ){return false;}//把任务存储到队列中return super.offer(runnable);}
}
还需要重写ThreadPollExector类的executor方法,来记录当前线程中需要处理的任务数:
package com.feisi.threadPool;import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;/*** 快速消费线程池*/
public class EagerThreadPoolExecutor extends ThreadPoolExecutor {public EagerThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}public EagerThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);}public EagerThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);}public EagerThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}private AtomicInteger submittedTaskCount = new AtomicInteger(0);//提供公开获取submittedTaskCount值public int getSubmittedTaskCount(){return submittedTaskCount.get();}@Overridepublic void execute(Runnable command) {
// System.out.println("进来了..");//任务数 + 1submittedTaskCount.incrementAndGet();try{Thread.sleep(500);super.execute(command);}catch (Exception e){ //拒绝//任务数-1submittedTaskCount.decrementAndGet();}}//表示任务执行完成的回调方法@Overrideprotected void afterExecute(Runnable r, Throwable t) {super.afterExecute(r, t);//任务数-1submittedTaskCount.decrementAndGet();}
}
运行结果:

可以看见,后面的线程也是由核心线程执行的,而没有去创建非核心线程,因为核心线程是空闲的,就不需要再去创建非核心线程了。
相关文章:
线程池原理及改造
目录 一 线程池执行原理 二 线程池改造(一) 三 线程池改造(二) 一 线程池执行原理 首先我们先了解一下线程池里面几个参数: 第一个是核心线程数,第二个是线程池最大线程数。(线程池里面的线程分为核心线程和非核心线程,既然核心…...
彻底理解mysql Buffer Pool (拓展)
彻底理解Buffer Pool (拓展) 一、Buffer Pool 的内存管理策略对数据库性能的影响 内存分配与回收:Buffer Pool 在申请内存时,需要考虑操作系统的内存分配策略。如果分配不合理,可能导致内存碎片,影响性能…...
信号量(二值信号量和计数信号量)和互斥量
信号量 信号量(Semaphore) 是一种实现任务间通信的机制, 可以实现任务之间同步或临界资源的互斥访问, 常用于协助一组相互竞争的任务来访问临界资源。 在多任务系统中, 各任务之间需要同步或互斥实现临界资源的保护&a…...
结构型模式-python版
在21种设计模式中, 结构型设计模式有7种, 分别是: 适配器模式代理模式桥接模式享元模式外观模式组合模式装饰器模式 下面逐一简要介绍: 1 适配器模式 适配器(Adapter)设计模式是一种结构型设计模式&…...
Java重修笔记 第五十四天 坦克大战(二)常用的绘图方法、画出坦克图形
常用的绘图方法 1.设置当前画笔的颜色,可多次调用 public abstract void setColor(Color c) 参数:c -颜色 2. 画一条直线 public abstract void drawLine(int x1, int y1, int x2, int y2) 参数:x1 - 第一个点的 x坐标。 y1 - 第一点的 y坐…...
OpenAI澄清:“GPT Next”不是新模型。
不,”GPT Next” 并不是OpenAI的下一个重要项目。 本周早些时候,OpenAI 日本业务的负责人长崎忠男在日本 KDDI 峰会上分享了一场演讲,似乎在暗示一个名为 “GPT Next” 的新模型即将出现。 但OpenAI的一位发言人已向Mashable证实࿰…...
<<编码>> 第 10 章 逻辑与开关(Logic and Switches) 示例电路
串联电路 info::操作说明 鼠标单击开关切换开合状态 需要两个开关同时闭合才能接通电路 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/code-hlchs-examples/assets/circuit/code-hlchs-ch10-01-series-circuit.txt 并联电路 in…...
深入浅出 Ansible 自动化运维:从入门到实战
在现代 IT 运维中,自动化是提升效率、降低错误率的关键。Ansible 作为一款流行的自动化工具,凭借其简洁的语法和强大的功能,成为了运维工程师的得力助手。本文将深入探讨 Ansible 的核心概念、实际应用以及一些实用的技巧,帮助你在…...
一句话描述设计模式
最近在看设计模式,其描述抽象程度令人欲罢不能,始终不得其意。于是尝试用一句话总结了一下,常规的就不说了,只是举了个例子。 单例模式 Spring中的单例bean使用了双重锁机制 工厂模式 Spring中的BeanFactory是简单工厂模式Bea…...
【Linux】Ubuntu 22.04 shell实现MySQL5.7 tar 一键安装
参考 https://blog.csdn.net/qq_35995514/article/details/134350572?spm1001.2014.3001.5501 在原作者基础上做了修改,加了一个删除原有mysql 的脚本 文章目录 一、安装下载**my.cnf 配置文件** 二、执行安装**install_mysql.sh 安装脚本**本机免密脚本 ssh_keyge…...
SQL Server开启网络访问
目前工作中很少用到SQL Server了,最近需要测试几个表,需要搭建一个SQL Server数据库服务,这里做个总结吧。 安装这里就不做详细介绍了,本文只介绍如何开启SQL Server网络访问。 1、云服务器安全组设置 如果是搭建在云服务器上&a…...
el-input设置type=‘number‘和v-model.number的区别
el-input设置typenumber’与设置.number修饰符的区别 1. 设置type‘number’ 使用el-input时想收集数字类型的数据,我们首先会想到typenumber,设置完type为number时会限制我们输入的内容只能为数字,不能为字符/汉字等非数字类型的数值&…...
6.第二阶段x86游戏实战2-理解程序流程
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 工具下载: 链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…...
Netty笔记01-Netty的基本概念与用法
文章目录 1. 概述1.1 Netty 是什么?1.2 Netty 的特点1.3 Netty 的作者1.4 Netty 的地位1.5 Netty 的优势1.6 Netty 的工作原理1.7 Netty 的应用场景1.8 Netty 的重要组件 2. 第一个程序2.1 目标2.2 服务器端2.3 客户端2.4 流程梳理💡 提示 1. 概述 1.1 …...
OpenHarmony鸿蒙( Beta5.0)RTSPServer实现播放视频详解
鸿蒙开发往期必看: 一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开发! “非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通) “一杯冰美式的时间” 了解鸿蒙HarmonyOS Next应用开发路…...
QT使用事件事件和绘制事件实现简易时钟
这个时钟实现的底层原理主要是利用 Qt 的绘图机制和定时器。首先,设置固定大小的窗口,创建定时器并连接到槽函数,定时器每秒钟触发一次,触发窗口重绘。在paintEvent函数中,使用QPainter进行绘图,绘制圆形表…...
kubeadm方式安装k8s
一、安装环境 环境准备:(有阿里云)centos7 k8s-master 192.168.1.11 k8s-node1 192.168.1.22 k8s-node2 192.168.1.33 二、前期准备 在k8s-master主机 [rootk8s-master ~]# vim /etc/hosts…...
如何使用go生成可执行文件
在 Go 中生成可执行文件非常简单。你可以使用 go build或者go install 命令。以下是步骤: 1. 步骤 1.1. 打开终端,导航到你的 Go 项目目录 确保你在包含 main 包的目录中,通常这是项目的根目录或包含 main.go 文件的目录。 1.2. 运行 go …...
手写Promise
构造器的实现 const PENDING pending; const FULFILLED fulfilled; const REJECTED rejectedclass MyPromise{#state PENDING;#result undefined;constructor(executor){const resolve (data) > {this.#changeState(FULFILLED, data);};const reject (reason) > …...
深度学习云服务器免费使用教程
#云服务器# #深度学习# #人工智能# #计算机视觉# 本文为各位学习深度学习的入门选手而创建,降低深度学习的入门门槛。 谷歌云服务器Colab: T4GPU。限额,需要科学上网,不能使用终端。 谷歌云服务器地址:欢迎使用 C…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
