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

手写一个线程池

自己手动写一个线程池的必要条件需要先了解我们使用的线程池的功能。为什么会有线程池?这是为了减少线程创建和销毁的开销。复用线程的目的。为了达到这个目的。预计方案是:需要一个存放任务的队列,主线程相当于生产者,在这个队列里面push任务,启动几个核心线程一直消费这个队列,然后执行其中的任务。

1、线程池的几个重要参数

(1)corePoolSize 核心线程数 含义是一直存活的线程数

(2)maxPoolSize 最大线程数 含义是最大运行的线程个数

(3)keepaliveTime 存活的时间 超过核心线程数的线程存活时间

(4)unit 单位

(5)blockingQueue 阻塞队列 放置任务使用

(6)threadFactory 线程工厂

(7)拒绝策略:abort策略表示拒绝任务,但是会抛异常,discard策略表示拒绝任务,不会抛异常。CallerRunPolicy策略表示会使用调用者去执行任务。DiscardoldPolicy策略表示抛弃老的任务,将新的任务添加进队列

上述几种参数决定了线程池要对外暴露什么样的接口,精选1-5必要参数手动写一个线程池。

2、代码逻辑

我这边编写的步骤是:

1、先将参数定义好,构造方法定义出来,除了上述必要的1-5的参数外,还需要一个消费者工作集合。为什么要保存成集合呢?主要用于在取消的时候遍历其集合,然后进行中断。

2、编写submit方法逻辑,首先参数肯定是Runable任务,判断逻辑:

(1)当运行的工作线程小于核心线程,则直接启动一个工作线程然后加入线程集合中。

(2)当1不成立后,那就需要加入阻塞队列等待了,如果阻塞队列是数组类型阻塞队列,这时有范围限制,如果不超过,则加入队列。

(3)当阻塞队列超了,则查看当前线程个数是否小于最大线程个数。如果不超过,则启动一个工作线程消费其任务,并且加入其工作集合中

(4)如果超过最大线程数,实际上走拒绝策略逻辑,这里我们就直接报错。

3、上述工作线程内部怎么消费呢?因为是阻塞队列,自然就是循环队列中取任务然后执行。而这里的取任务,分成了poll和take。这是为什么?从上面步骤可知核心线程数超过之后也会存在启动工作线程的情况,那这些线程有一个保活时间,时间一到,只要此线程空闲,线程则会跳出循环结束执行。而这里阻塞队列的poll可以实现上述逻辑。take则在没有任务时就阻塞。

下面是按照其上述逻辑的代码,大家可以参考。

package org.example.Thread1;import java.util.HashSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;public class SelfThreadPoolExecutor {//一直存活的核心线程数private int corePoolSize;//运行存在的最大的线程数private int maxPoolSize;private AtomicInteger status = new AtomicInteger();private AtomicInteger workCount = new AtomicInteger();private final static Integer RUNNING = 0;private final static Integer STOP = 1;private int keepAliveTime;private BlockingQueue<Runnable> blockingQueue;private HashSet<Worker> hashSet = new HashSet<>();private ReentrantLock mainLock = new ReentrantLock();public SelfThreadPoolExecutor(int corePoolSize, int maxPoolSize, int keepAliveTime, BlockingQueue<Runnable> blockingQueue) {this.corePoolSize = corePoolSize;this.maxPoolSize = maxPoolSize;this.keepAliveTime = keepAliveTime;this.blockingQueue = blockingQueue;}public void submit(Runnable runnable) {if (status.get() == STOP) {throw new RuntimeException("不能添加新的任务了");}if (workCount.get() < corePoolSize && addWork(runnable, true)) {return;}if (blockingQueue.offer(runnable)) {return;}if (workCount.get() < maxPoolSize && addWork(runnable, false)) {return;}throw new RuntimeException("拒绝此任务");}public boolean addWork(Runnable runnable, boolean core) {if (status.get() == STOP) {return false;}while (true) {if (workCount.get() > (core ? corePoolSize : maxPoolSize)) {return false;}boolean inc = workCount.compareAndSet(workCount.get(), workCount.get()+1);if (!inc) {continue;}break;}mainLock.lock();try {Worker worker = new Worker(runnable);worker.thread.start();hashSet.add(worker);} finally {while (true) {boolean inc = workCount.compareAndSet(workCount.get(), workCount.get()-1);if (!inc) {continue;}break;}mainLock.unlock();}return true;}public void shutdown() {mainLock.lock();try {while (true) {if (status.get() == STOP) {break;}boolean stop = status.compareAndSet(status.get(), STOP);if (stop) {break;}}for (Worker worker : hashSet) {if (!worker.thread.isInterrupted()) {worker.thread.interrupt();}}}finally {mainLock.unlock();}}private final class Worker implements Runnable{Runnable firstTask;final Thread thread;public Worker(Runnable firstTask) {this.firstTask = firstTask;thread = new Thread(this);}@Overridepublic void run() {runWork(this);}public void runWork(Worker w) {Thread wt = w.thread;Runnable task = w.firstTask;w.firstTask = null;try {while (task != null || (task = getTask()) != null) {try {if (wt.isInterrupted()) {break;}if (status.get() == STOP) {break;}task.run();} finally {task = null;}}} finally {mainLock.lock();try {hashSet.remove(w);} finally {mainLock.unlock();}}}public Runnable getTask() {boolean timedOut = false;while (true) {try {if (status.get() == STOP) {return null;}if (timedOut) {return null;}Runnable task = null;if (workCount.get() > corePoolSize) {task = blockingQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);} else {task = blockingQueue.take();}if (task != null) {return task;}timedOut = true;} catch (InterruptedException e) {timedOut = true;}}}}
}

 

 

相关文章:

手写一个线程池

自己手动写一个线程池的必要条件需要先了解我们使用的线程池的功能。为什么会有线程池&#xff1f;这是为了减少线程创建和销毁的开销。复用线程的目的。为了达到这个目的。预计方案是&#xff1a;需要一个存放任务的队列&#xff0c;主线程相当于生产者&#xff0c;在这个队列…...

Spring Boot 多环境配置

Spring Boot 多环境配置 在现代的软件开发中&#xff0c;通常需要将应用程序部署到不同的环境中&#xff0c;如开发环境、生产环境和测试环境等。每个环境可能需要不同的配置参数&#xff0c;例如数据库连接信息、日志级别等。在 Spring Boot 中&#xff0c;我们可以通过简单的…...

【Python】一文带你详解sys.executable函数的作用

【Python】一文带你详解sys.executable函数的作用 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的订阅和支…...

thingsboard如何自定义udp-transport

0、参考netty实现udp的文章 https://github.com/narkhedesam/Netty-Simple-UDP-TCP-server-client/blob/master/netty-udp/src/com/sam/netty_udp/server/MessageDecoder.java 调试工具使用的是:卓岚TCP&UDP调试工具 1、在common\transport下面创建udp模块,仿照mqtt的创…...

【汇编】#3 8086与数据有关的寻址方式

文章目录 操作码与操作数1. 8086处理器的与数据有关的寻址方式1.1 立即数寻址方式1.2 寄存器寻址方式 2. 有效&#xff08;偏移&#xff09;地址&#xff08;effective address&#xff0c;EA&#xff09;与缺省段寄存器选择tips:段跨越前缀2.1 直接寻址tips:直接寻址与立即寻址…...

【数据结构】单链表的层层实现!! !

关注小庄 顿顿解馋(●’◡’●) 上篇回顾 我们上篇学习了本质为数组的数据结构—顺序表&#xff0c;顺序表支持下标随机访问而且高速缓存命中率高&#xff0c;然而可能造成空间的浪费&#xff0c;同时增加数据时多次移动会造成效率低下&#xff0c;那有什么解决之法呢&#xff…...

丰田研究所(TRI)最新成果——可实现全身操控的软体机器人Punyo

文 | BFT机器人 人形机器人在近年的科技浪潮中迅速崛起&#xff0c;成为了各界瞩目的焦点&#xff0c;众多企业纷纷推出自家的机器人模型&#xff0c;但仔细观察&#xff0c;不难发现它们中的许多在操作方式上仍显得颇为相似。这些典型的人形机器人&#xff0c;以其机械臂和抓…...

【PyTorch实战演练】深入剖析MTCNN(多任务级联卷积神经网络)并使用30行代码实现人脸识别

文章目录 0. 前言1. 级联神经网络介绍2. MTCNN介绍2.1 MTCNN提出背景2.2 MTCNN结构 3. MTCNN PyTorch实战3.1 facenet_pytorch库中的MTCNN3.2 识别图像数据3.3 人脸识别3.4 关键点定位 0. 前言 按照国际惯例&#xff0c;首先声明&#xff1a;本文只是我自己学习的理解&#xff…...

MFC中字符串string类型和CString类型互转方法

在Microsoft Foundation Classes (MFC)中&#xff0c;CString是一个非常方便的类&#xff0c;用于处理C风格的字符串。有时&#xff0c;你可能需要在MFC的CString和C标准库中的std::string之间进行转换。下面是如何在两者之间进行转换的方法&#xff1a; CString转std::string…...

Jmeter-使用http proxy代理录制脚本

Jmeter-使用http proxy代理录制脚本 第1步&#xff1a;打卡jmeter工具新增1个线程组 第2步&#xff1a;给线程组添加1个HTTP请求默认值 第3步&#xff1a;设置下HTTP请求默认值第4步&#xff1a;在工作台中新增1个----HTTP代理服务器 第5步&#xff1a;设置HTTP代理服务器 …...

C++训练营:new 运算符

大家好&#xff1a; 衷心希望各位点赞。 您的问题请留在评论区&#xff0c;我会及时回答。 一、new 运算符 new 运算符用于动态分配一片内存空间&#xff0c;并返回这片内存空间的首地址&#xff0c;可将该首地址存入一个指针变量中&#xff0c;主要有以下三种格式。 二、格…...

C# 用Trace.WriteLine输出调试信息无法查看

写程序就会遇见BUG&#xff0c;这时候在代码不同部位输出一些标记的信息对查找错误非常有必要&#xff0c;一般情况下我们都是使用Console.WriteLine()方法来打印信息到控制台窗口&#xff0c;但有时候使用Console.WriteLine()方法会存在不方便的情况&#xff0c;比如鄙人遇到的…...

【Echarts】柱状图上方显示数字以及自定义值,标题和副标题居中,鼠标上显示信息以及自定义信息

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《前端》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握…...

HTML 语义化:构建优质网页的关键

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…...

Flutter入门学习——Flutter和Dart

因为工作的需要&#xff0c;也为了个人发展&#xff0c;现在的话&#xff0c;转战Flutter跨端开发了&#xff0c;虽然目前的项目只发了android端&#xff0c;但是那天尝试了一下Ios的打包流程&#xff0c;也能运行&#xff0c;只是IOS那边的打包稍微复杂一些。 差不多学习了一…...

C++中的内存管理方式

一、C内存管理方式简介 C语言中的内存管理方式在C中可以继续使用&#xff0c;但是在有些地方就无能为力&#xff0c;而且使用起来比较麻烦。因此C中引入了自己的内存管理方式&#xff0c;通过new和delete操作符进行动态内存管理。 二、new语法 new可以申请1个或多个空间&…...

macos m1 arm芯片 使用jpype报错 FileNotFoundError: [Errno 2] JVM DLL not found

startJVM(jpype.getDefaultJVMPath()) 报错 Traceback (most recent call last):File "/Users/thomas990p/PycharmProjects/tuya/volcano-biz-scripts/WenKongFa/FinalCode/java2python/CallJavaAPI.py", line 12, in <module>startJVM(jpype.getDefaultJVMPa…...

Hive中UNION ALL和UNION的区别

1.概述 Hive官方提供了一种联合查询的语法&#xff0c;原名为Union Syntax&#xff0c;用于联合两个表的记录进行查询&#xff0c;此处的联合和join是不同的&#xff0c;join是将两个表的字段拼接到一起&#xff0c;而union是将两个表的记录拼接在一起。 换言之&#xff0c; jo…...

selenium高级应用

常见控件应用 复杂的控件操作1.操作Ajax选项2.滑动滑块操作 WebDriver的特殊操作元素class值包含空格property、attribute、text的区别定位动态id 截图功能页面截图页面截图&#xff0c;返回截图的二进制数据页面截图&#xff0c;返回base64的字符串截取指定元素。先定位元素&a…...

微信小程序重新加载当前页面、刷新当前页面

重新加载页面 使用wx.reLanuch&#xff08;&#xff09;&#xff0c;url: 路径当前页面跳转, 页面所有数据重新初始化&#xff0c;已配置的数据不会保存 wx.reLaunch({url: /pages/orders/createOrder/createOrder, // 当前页面的路径}) reLanuch()的方法&#xff0c;会有一个…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...

通过MicroSip配置自己的freeswitch服务器进行调试记录

之前用docker安装的freeswitch的&#xff0c;启动是正常的&#xff0c; 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...

机器学习的数学基础:线性模型

线性模型 线性模型的基本形式为&#xff1a; f ( x ) ω T x b f\left(\boldsymbol{x}\right)\boldsymbol{\omega}^\text{T}\boldsymbol{x}b f(x)ωTxb 回归问题 利用最小二乘法&#xff0c;得到 ω \boldsymbol{\omega} ω和 b b b的参数估计$ \boldsymbol{\hat{\omega}}…...