java自定义线程池详解

目录

  • 线程池
    • 使用线程池的目的
    • 线程池工作原理
    • 线程池常用方法
    • 自定义线程池
      • 等待队列
      • 拒绝策略
      • 线程工厂

线程池

使用线程池的目的

  • 资源复用,降低开销。重复利用已创建的线程,避免线程频繁地创建和销毁带来的性能开销。
  • 方便线程的可管理性。线程是稀缺资源,使用线程池可以进行统一的分配,调优和监控。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

线程池工作原理

工作原理(借用guide哥的图):

图解线程池实现原理

注意:

  • 刚开始创建线程池的时候不会立马创建好核心线程,而是有任务来了之后再创建核心线程
  • 线程池小于corePoolSize时,就算有线程空闲,也会一定会创建新的线程
  • 当等待队列也满了的时候,又来一个任务,会新创建一个非核心线程,且用于执行刚刚到来的任务,而不是队头的任务
  • 如果设置了allowCoreThreadTimeOut(true),那么核心线程空闲时间达到keepAliveTime也会关闭,但默认不会

线程池常用方法

//使用工具类创建线程池
ExecutorService executor1 = Executors.newFixedThreadPool(3);

//定义任务
Runnable task = ()->{
	System.out.println("线程"+Thread.currentThread().getName()+"正在执行");
};

executor.excute(XXX); //执行任务,没有返回值
executor.submit(XXX);//执行任务,返回一个Future,用于获取线程的执行情况

executor.shutdown();//正常关闭,停止提交新任务,而已提交的任务可以正常执行,非阻塞
executor.shutdownNow();//立即强制关闭,停止提交新任务,正在运行的任务停止,等待队列也销毁,非阻塞
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); // 阻塞等待所有任务完成并且shutdown,如果超过到达时间,则返回false

当等待队列是无界队列时,非核心线程会创建吗?

不会,因为非核心线程只有在队列满的时候才会创建,使用了无界队列就相当于默认只有核心线程生效

自定义线程池

总结四种线内置程池都相对比较极端,所以一般都建议使用自定义线程池,保证一定的核心线程数(保证活跃)和一定的非核心线程数(提供伸缩)。

工作流程概述:刚开始没有线程,添加任务后创建核心线程,核心线程满了就加到等待队列,等待队列也满了就创建临时线程去执行,临时线程也满了就执行拒绝策略。

ThreadPoolExector线程池完整参数如下:

    public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数
                              long keepAliveTime,//非核心线程(临时线程)空闲存活时间
                              TimeUnit unit,//存活时间单位
                              BlockingQueue<Runnable> workQueue,//等待队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler//拒绝策略
                             ) 

各个参数详解:

  • corePoolSize:核心线程数,线程池中保持活跃的线程数量。即使线程空闲,核心线程也不会被回收。
  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。超过此数量的请求将被放入等待队列
  • keepAliveTime:非核心线程的空闲时间,超过此时间后,非核心线程会被回收。
  • unitkeepAliveTime 的时间单位,例如秒、毫秒等。
  • workQueue:等待队列,用于存放被提交但尚未被执行的任务。可以是不同类型的队列,如 LinkedBlockingQueueArrayBlockingQueue 等。
  • handler:饱和策略,当线程池和等待队列都满时,任务提交失败时所使用的处理策略。
  • threadFactory:用于创建新线程的工厂,允许自定义线程的创建,例如设置线程名称或优先级。

注意:前面五个参数是必须指定的,而最后两个参数可以不指定,因为有默认值。

三大核心参数:核心线程用于保证快速响应和资源复用,而非核心线程用于应对突发情况,等待队列则是作为一个缓冲,同时控制处理任务的数量和顺序。

等待队列

默认

  • LinkedBlockingQueue:一个基于链表的阻塞队列,支持 FIFO(先进先出)排序。可以设置容量,默认是 Integer.MAX_VALUE。适用于任务较多且队列可以扩展的场景。
  • ArrayBlockingQueue:一个基于数组的阻塞队列,容量固定,适用于任务数量可预估的场景。
  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作都必须等待另一个线程的对应移除操作,适合高并发场景
  • PriorityBlockingQueue:支持优先级排序的阻塞队列,适合需要按优先级处理任务的场景。

ArrayBlockingQueue和带有容量的LinkedBlockingQueue有什么区别,怎么选择?

LinkedBlockingQueue插入删除方便,对高并发场景友好,而ArrayBlockingQueue是用循环数组实现,插入删除的代价比较大,涉及到模运算(取余)。

ArrayBlockingQueue内存占用更小,因为是基于数组实现。更适合生产者-消费者模型中的任务数量相对固定的场景。

SynchronousQueue队列特殊在哪?什么时候用?

特殊之处:

  • SynchronousQueue` 的容量为 0,这意味着它不存储任何元素。每个插入操作必须等待一个对应的移除操作才能完成,反之亦然。
  • 直接交付:当一个线程试图插入一个元素到 SynchronousQueue 时,它会被阻塞,直到另一个线程尝试移除这个元素。这种设计实现了线程之间的直接交付。
  • 没有内部缓冲:与其他阻塞队列(如 ArrayBlockingQueueLinkedBlockingQueue)不同,SynchronousQueue 不维护任何内部元素,因此不能进行批量处理或存储任务。

使用效果:当核心线程满了的时候,每提交一个任务,的认为阻塞队列是满的,当非核心线程充足时,会马上创建一个非核心线程去执行这个任务。(内部的CachedThreadPool就是这样)

适用场景:在需要低延迟和高并发的应用场景中,SynchronousQueue 可以减少任务的等待时间,因为生产者和消费者必须在同一时间交互。

拒绝策略

当线程池和等待队列都满时,可以选择不同的拒绝策略对新任务进行处理:

  1. AbortPolicy(默认):拒绝任务并且抛出 RejectedExecutionException,适合希望知道任务失败的场景。
  2. DiscardPolicy:丢弃任务,不抛出异常,适合对任务丢失不敏感的场景。
  3. CallerRunsPolicy:将任务交给线程池的调用者(比如主线程或者其它不属于本线程池管理的线程)自己去执行,适合希望减轻线程池压力的场景。
  4. DiscardOldestPolicy:丢弃掉等待队列中队头的任务,也就是最旧的任务,然后加入等待队列。适合希望保留最新任务的场景。
  5. DelayQueue:一个支持延迟处理的阻塞队列,任务必须在指定延迟后才能获取,适合需要定时执行的任务场景。
  6. 自定义拒绝策略,实现RejectedExecutionHandler接口。

注意:如上队列都属于阻塞队列,能够保证线程安全,并且符合生产者消费者模式,队列满的时候,会阻塞入队操作;队列空的时候,会阻塞出队操作。

自定义拒绝策略可以有哪些思路?

拒绝任务,并且打印一些有用日志。

import java.util.logging.Logger;

public class LoggingRejectPolicy implements RejectedExecutionHandler {
    private static final Logger logger = Logger.getLogger(LoggingRejectPolicy.class.getName());

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        logger.warning("Task " + r.toString() + " rejected. Current pool size: " + executor.getPoolSize());
    }
}

等待一段时间后再尝试提交任务。

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

public class RateLimitPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            Thread.sleep(100); // 限流,稍等后重试
            executor.execute(r);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

线程工厂

线程工程用于控制线程池中的线程是如何创建的,也就是给各个程取一个名字。

这个参数一般不需要传递,因为有一个Executors有一个默认的线程工厂(是静态类,所有线程池共用)

默认规则:pool-线程池序号-thread-线程序号

实现逻辑:执行一次DefaultThreadFactory构造方法,线程池的序号就+1(原子操作),而且这个序号是static修饰,所有工厂对象共享;然后每调用一次newThread,线程的序号就+1(原子操作),这个序号不是static,各个工厂都从1开始

    /**
     * The default thread factory
     */
    static class DefaultThreadFactory implements ThreadFactory {
        //原子类,static,记录线程池的个数
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        //原子类,记录某个线程的个数
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }
		
        //创建线程的京具体方法
        public Thread newThread(Runnable r) {
            //拼接出线程的名字
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())//设置线程都是飞守护线程,不会随着主线程终止而终止
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

自定义线程工厂实例

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,
                10,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1),
            	//自定义线程工厂
                new ThreadFactory() {
                    AtomicInteger number = new AtomicInteger(1);
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r,"myThreadPool_"+number.getAndIncrement());
                    }
                });

        // 提交多个任务
        for (int i = 0; i < 10; i++) {
            int taskId = i;
            threadPoolExecutor.execute(() -> {
                System.out.println("任务 " + taskId + " 在 " + Thread.currentThread().getName() + " 执行");
                try {
                    Thread.sleep(5000); // 模拟任务执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池
        threadPoolExecutor.shutdown();
    }
}
```在这里插入图片描述

### 线程池大小该如何设置?

假设核心数为n,可参考公式:

CPU密集类型(IO比较少),可以设置n+1

IO密集类型(读取文件或者数据库的IO比较多),可以设置为线程的k的倍数。比如从2 * n开始,慢慢尝试4 * n、8 * n ... 

至于k取多少,可以根据如下公式确定:


![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6b85ef0bdffa4153a660e6986ee6c85d.png)



> cpu密集型任务为什么设置n+1?

当某个线程故障或者缺页中断了,此时剩余一个线程就可以顶上

> IO密集类型为什么设置n的整数倍?

**因为对于IO密集类型的任务来说,CPU不是一直在运行的,可能在等待**比如CPU运行1秒,IO运行3秒,此时完全可以设置4个线程去执行任务,让3s的等待时间也利用起来。

> Windows电脑假设是4核心,8个逻辑处理器,那么最多支持8个线程并发执行吗?为什么java中可以创建100个线程同时执行?

不是,八个逻辑处理器意味着最多可以有八个线程**并行执行**(同一时刻),而创建的100个线程会在这八个逻辑处理器中进行并发执行(不断切换)。操作系统会通过上下文切换在不同线程之间切换,从而有效利用处理器资源。虽然同时运行的线程数有限,但通过快速切换,多个线程仍能“看起来”像是在同时执行。

> 如果cpu只有一个核心,可以支持多线程吗?

就算只有一个核心(假设逻辑处理器也是1),也是可以支持多个线程的,只不过是并发执行,线程数量越多,各个线程的等待其它线程时间片的时间就越长,性能开销就越大。

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

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

相关文章

【GVINS】

【GVINS】 1. GVINS的系统特点2. GVINS的融合导航存在问题3. GVINS的信号的组成4. GVINS的信号的组成 原理推导知乎 1. GVINS的系统特点 概述了一种名为GVINS的系统&#xff0c;它旨在解决视觉-惯性里程计&#xff08;VIO&#xff09;在长时间运行时出现的漂移问题。GVINS通过…

三菱变频器Modbus-RTU 通讯规格

能够从变频器的 RS-485 端子使用 Modbus-RTU 通讯协议&#xff0c;进行通讯运行和参数设定。 NOTE: 1、使用 Modbus-RTU 通讯协议时&#xff0c;请设定Pr.549 协议选择 “1” 2、从主机按地址0(站号0)进行hodbus-RTU通讯时&#xff0c;为广播通讯&#xff0c;变频器不向主机发…

软件设计画图,流程图、甘特图、时间轴图、系统架构图、网络拓扑图、E-R图、思维导图

目录 一、流程图 二、甘特图 三、时间轴图 四、系统架构图 五、网络拓扑图 六、E-R图 七、思维导图 一、流程图 是一种用符号表示算法、工作流或流程的图形。用不同的图形表示不同含义&#xff0c;如椭圆表示开始和结束、菱形表示判断等。 画图工具WPS office 应用市场…

idea启动oom了解决

解决 Error:java: java.lang.OutOfMemoryError: WrappedJavaFileObject[org.jetbrains.jps.javac.InputFileObject[file:///D:/mingan/pb/backend/src/main/java/com/cy/backend/service/impl/StorageServiceImpl.java]]pos36199: WrappedJavaFileObject[org.jetbrains.jps.j…

松材线虫目标检测数据集,12522张图-纯手工标注

松材线虫目标检测数据集&#xff0c;12522张图像&#xff0c;专家纯手工标注。 松材线虫目标检测数据集 数据集描述 该数据集是一个专门用于松材线虫&#xff08;Bursaphelenchus xylophilus&#xff09;检测的数据集&#xff0c;旨在帮助研究人员和开发者训练和评估基于深度…

地平线4登录xbox后提示需要登录档案怎么解决

这个游戏是真nt&#xff08;在联机上&#xff09;&#xff0c;典型搞联机2小时游玩半小时&#xff0c;多半时间都花费在联机上了&#xff0c;不是为了联机和朋友跑车&#xff0c;早给他卸载了。 本人的游戏问题&#xff1a;看了一些视频感觉没什么作用&#xff0c;我的现象就是…

gcc配合cython编译python源代码

以前我们一般用Nuitka或者Pyinstaller来将python源码编译成二进制可执行文件。今天我们学习如何直接用gcc来编译。 很简单的一个python程序&#xff0c;结构如下。包含一个model.py和main.py 步骤1&#xff1a;处理main.py 处理main.py。即主程序入口 cython -D -2 --embe…

BGP 路由反射器

转载&#xff1a;BGP 路由反射器 / 实验介绍: / 原理概述 缺省情况下&#xff0c;路由器从它的一个 IBGP 对等体那里接收到的路由条目不会被该路由器再传递给其他IBGP对等体&#xff0c;这个原则称为BGP水平分割 原则&#xff0c;该原则的根本作用是防止 AS 内部的 BGP 路由…

化学分子结构检测系统源码分享

化学分子结构检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

CleanClip For Mac 強大的剪貼簿助手Paste替代工具 v2.2.1

软件介绍&#xff1a; CleanClip是一款专为Mac设计的强大剪贴板管理工具&#xff0c;旨在提升用户的工作效率和生产力。这款应用完全采用原生Swift编写&#xff0c;为Mac用户提供了流畅、快速且直观的使用体验。CleanClip不仅支持文本内容的管理&#xff0c;还能处理图片、文件…

C++11——lambda

lambda lambda的介绍lambda的使用lambda的细节->捕捉列表 lambda的介绍 lambda是匿名函数&#xff0c;再适合的场景去使用可以提高代码的可读性。 场景&#xff1a; 假设有一个Goods类需要进行按照价格、数量排序 class Goods {string name;size_t _price;//价格int num;/…

攻击者将恶意软件分解成小块并绕过您的安全网关

近二十年来&#xff0c;安全 Web 网关 (SWG) 一直用于监控网络流量、拦截恶意文件和网站以及保护企业免受 Web 威胁。 如今&#xff0c;许多企业都相信他们的 SWG 能够捕获所有已知恶意软件&#xff0c;正如其 SLA &#xff08;服务质量保证&#xff09;中所承诺的那样。 然而…

Docker 里面按照ifconfig

1. 进入Docker 容器内部 docker exec -it xxx bash2. 安装 net-tools iputils-ping apt-get update && apt-get install -y net-tools apt-get update && apt-get install -y iputils-ping 3. 执行ifconfig 执行ping

WebLogic 后台弱⼝令GetShell

漏洞描述 通过弱⼝令进⼊后台界⾯ , 上传部署war包 , getshell 影响范围 全版本&#xff08;前提后台存在弱⼝令&#xff09; 环境搭建 cd vulhub-master/weblogic/weak_password docker-compose up -d 漏洞复现 默认账号密码&#xff1a;weblogic/Oracle123 weblogic…

ChatCADChatCAD+:Towards a Universal and Reliable Interactive CAD using LLMs

ChatCAD&#xff08;论文链接&#xff1a;[2302.07257] ChatCAD: Interactive Computer-Aided Diagnosis on Medical Image using Large Language Models (arxiv.org)&#xff09; 网络流程图&#xff1a; 辅助阅读&#xff1a; 基于大型语言模型的医学图像交互式计算机辅助诊…

数据结构--双链表

目录 一、引言 二 、链表的分类 1.单向或双向 2.带头或不带头 3.循环或不循环 三、双链表的概念与基本结构 1.概念 2.基本结构 三、双链表的常见操作 1.创建节点 2.初始化 3.头插 4.尾插 5.头删 6.尾删 7.打印 8.查找 9.插入节点 10.删除节点 11.销毁链…

gin配置swagger文档

一、基本准备工作 1、安装依赖包 go get -u github.com/swaggo/swag/cmd/swag go get -u github.com/swaggo/gin-swagger go get -u github.com/swaggo/files2、在根目录上配置swagger的路由文件 //2.初始化路由router : initialize.Routers()// 配置swaggerdocs.SwaggerInfo…

京东商品属性的详细api数据解析:颜色、尺寸与材质

京东&#xff08;JD.com&#xff09;作为一个大型电商平台&#xff0c;其商品信息通过API接口提供给开发者或第三方服务使用&#xff0c;以便进行商品搜索、展示、分析等操作。然而&#xff0c;直接访问京东的详细商品属性&#xff08;如颜色、尺寸、材质等&#xff09;API通常…

uniapp|微信小程序 实现输入四位数 空格隔开

<template><page-meta :page-style"cssVar"></page-meta><view class"container"><u-navbartitle"优惠券兑换"placeholderbgColor"#fff":autoBack"true":titleStyle"{fontFamily: SourceHa…

Maven Helper 插件

推荐指数&#xff1a;★★★★★ 分析依赖冲突插件 Maven Helper插件就可免去命令行困扰。通过界面解决依赖冲突。 点击此按钮&#xff0c;切换到此工具栏 可进行相应操作&#xff1a; Conflicts&#xff08;查看冲突&#xff09;All Dependencies as List&#xff08;列表形…