线程池,及7大参数,4大拒绝策略详解

news2024/11/28 2:56:24

线程池,及7大参数,4大拒绝策略详解

1. 前言

1.1 什么是线程池?

线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。在JAVA中主要是使用ThreadPoolExecutor类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池(不推荐使用)。

线程池的优点:
降低资源消耗,复用已创建的线程来降低创建和销毁线程的消耗。
提高响应速度,任务到达时,可以不需要等待线程的创建立即执行。
提高线程的可管理性,使用线程池能够统一的分配、调优和监控。

1.2 为什么使用线程池?

没有线程池时

image-20231205201638662

从上面可以看出之前显示的创建线程的一些缺点:

1)不受控制风险,对于每个创建的线程没有统一管理的地方,每个线程创建后我们不知道线程的去向。
2)每执行一个任务都需要创建新的线程来执行,创建线程对系统来说开销很高

而若是用线程池来管理线程

image-20231205201739050

执行相同任务时可以复用线程并不用频繁创建和销毁。

线程池的好处:

降低资源的消耗
提高响应的速度
方便管理
线程复用、控制最大并发数、管理线程

强制----线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。

如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

2.JAVA线程池概述

Java中线程池的核心实现类是ThreadPoolExecutor,可以通过该类地构造方法来构造一个线程池,我们先来看下ThreadPoolExecutor的整个继承体系

image-20231205202330250

Executor接口

  • java.util.concurrent.Executor 接口提供了一种将任务的提交与任务的执行分离的机制。它包含一个单一的方法 execute(Runnable command),用于执行传递的任务。

ExecutorService接口

  • java.util.concurrent.ExecutorService 接口扩展了Executor接口,提供了更丰富的功能来管理线程池。

AbstractExecutorService类

  • AbstractExecutorService 类是 ExecutorService 接口的一个抽象实现,提供了一些默认实现和辅助方法,使得实现自定义的线程池变得更加容易。该类实现了部分 ExecutorService 接口中的方法,留下了一些抽象方法需要具体的子类来实现。

ThreadPoolExecutor类

  • java.util.concurrent.ThreadPoolExecutorExecutorService接口的一个实现,它提供了一个灵活且可扩展的线程池实现。
  • 通过 ThreadPoolExecutor 的构造方法可以创建一个线程池,配置核心线程数、最大线程数、线程空闲时间、工作队列等参数,以满足不同场景的需求。

2.1七大参数

ThreadPoolExecutor类提供了七个参数,这些参数用于配置线程池的行为。

  1. corePoolSize(核心线程数)
    • 线程池的基本大小,即在没有任务需要执行时,线程池的大小是多少。
    • 如果调用了prestartAllCoreThreads()方法,线程池会在启动时提前创建并启动所有的核心线程。
  2. maximumPoolSize(最大线程数)
    • 线程池允许创建的最大线程数。如果队列满了,并且活动线程数小于最大线程数,线程池会创建新的线程来执行任务。
    • 如果使用了无界队列(例如LinkedBlockingQueue),则该参数就不起作用。
  3. keepAliveTime(空闲线程存活时间)
    • 当线程池中的线程数量大于核心线程数时,多余的空闲线程在终止之前等待新任务的最长时间。超过这个时间就会被回收。
    • 如果设置了allowCoreThreadTimeOut为true,则核心线程也会超时终止。
  4. unit(空闲线程存活时间的单位)
    • keepAliveTime 参数的时间单位,通常是 TimeUnit.SECONDS 或 TimeUnit.MILLISECONDS。
  5. workQueue(阻塞队列)
    • 用于保存等待执行的任务的阻塞队列。可以选择不同类型的队列,例如 LinkedBlockingQueue、ArrayBlockingQueue 等。
    • 这是线程池的关键参数之一,不同的队列类型会影响线程池的行为。
  6. threadFactory(线程工厂)
    • 用于创建新线程的工厂。可以通过实现 ThreadFactory 接口来自定义线程的创建过程,例如为线程指定名称、优先级等。
  7. handler(拒绝策略)
    • 当工作队列满并且线程池中的线程数达到最大线程数时,用于处理新提交的任务的策略。

简单使用线程

public class Main {
    public static void main(String[] args) {
        int corePoolSize = 4; //核心线程数
        int maximumPoolSize = 8; //最大线程数
        long keepAliveTime = 10000; //空闲线程存活时间
        TimeUnit unit = TimeUnit.MILLISECONDS; //空闲线程存活时间的单位
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(8); //阻塞队列
        ThreadFactory threadFactory = new ThreadFactory() {  //线程工厂
            @Override
            public Thread newThread(Runnable r) {
                System.out.println("创建线程:" + r);
                return new Thread(r);
            }
        };
        RejectedExecutionHandler handler = null;//拒绝策略
        ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                threadFactory,
                new CustomerThreadPool.MyAbortPolicy()
        );
        for (int i = 0; i < 16; i++) {
            myThreadPool.execute(() -> {
                // 执行的线程
                for (int j = 0; j < 10; j++) {
                    System.out.println(Thread.currentThread() + ":执行" + j + "次");
                }
            });
        }
        myThreadPool.shutdown();
    }
}

image-20231205205947025

2.2四大拒绝策略

Java中的线程池在任务提交过程中,如果线程池已满且无法继续创建新线程,就会触发拒绝策略。Java提供了四种预定义的拒绝策略,它们分别是:

  1. AbortPolicy(中止策略)

    • 默认的拒绝策略,会直接抛出 RejectedExecutionException 异常,阻止系统正常运行。
  2. CallerRunsPolicy(调用者运行策略)

    • 将任务回退给调用线程来执行。在这个策略中,任务提交者会自己去执行该任务,从而降低新任务的提交速度,以适应系统的处理能力。
  3. DiscardPolicy(丢弃策略)

    • 直接丢弃新的任务,没有任何处理。如果系统对任务丢失不敏感,可以使用这个策略。
  4. DiscardOldestPolicy(丢弃最老策略)

    • 丢弃队列中最老的任务,然后尝试重新提交当前任务。这样可以腾出队列空间来接收新的任务,但可能会丢失一些等待执行的任务。

这些拒绝策略提供了不同的处理方式,可以根据实际场景和需求选择合适的策略。通过 setRejectedExecutionHandler 方法,可以在创建 ThreadPoolExecutor 实例时指定拒绝策略。

2.3测试四大拒绝策略

  1. AbortPolicy(中止策略)

    public class Main {
        public static void main(String[] args) {
            // 创建线程池
            ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 1,
                    TimeUnit.MINUTES, new LinkedBlockingDeque<>(1), Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy());
            // 创建4个任务
            for(int i = 0; i < 4; i++) {
                pool.execute(() -> System.out.println(Thread.currentThread().getName() + " ... "));
            }
            // 关闭线程池
            pool.shutdown();
        }
    }
    

    执行结果:
    image-20231205213042860

    分析:

    最大的线程数是2,阻塞队列中可以存一个,最多就可接收3个任务,当有4个任务时,就会执行拒绝策略,此拒绝策略为丢弃任务,抛出异常

  2. CallerRunsPolicy(调用者运行策略)

    public class Main {
        public static void main(String[] args) {
            // 创建线程池
            ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 1,
                    TimeUnit.MINUTES, new LinkedBlockingDeque<>(1), Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.CallerRunsPolicy());
            // 创建4个任务
            for(int i = 0; i < 4; i++) {
                pool.execute(() -> System.out.println(Thread.currentThread().getName() + " ... "));
            }
            // 关闭线程池
            pool.shutdown();
        }
    }
    

    执行结果:

    image-20231205213210604

    分析:

    最大的线程数是2,阻塞队列中可以存一个,最多就可接收3个任务,当有4个任务时,就会执行拒绝策略,因为是主线程创建的线程,所以当任务没有线程执行的时候,会由创建此线程的线程来执行,也就是由主线程来执行,主线程名为main

  3. DiscardPolicy(丢弃策略)

    public class Main {
        public static void main(String[] args) {
            // 创建线程池
            ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 1,
                    TimeUnit.MINUTES, new LinkedBlockingDeque<>(1), Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardPolicy());
            // 创建4个任务
            for(int i = 0; i < 4; i++) {
                pool.execute(() -> System.out.println(Thread.currentThread().getName() + " ... "));
            }
            // 关闭线程池
            pool.shutdown();
        }
    }
    

    执行结果:

    image-20231205213420782

    分析:

    最大的线程数是2,阻塞队列中可以存一个,最多就可接收3个任务,当有4个任务时,就会执行拒绝策略,丢弃新产生的任务

  4. DiscardOldestPolicy(丢弃最老策略)

    public class Main {
        public static void main(String[] args) {
            // 创建线程池
            ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 1,
                    TimeUnit.MINUTES, new LinkedBlockingDeque<>(1), Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardOldestPolicy());
            // 创建4个任务
            for(int i = 0; i < 4; i++) {
                pool.execute(() -> System.out.println(Thread.currentThread().getName() + " ... "));
            }
            // 关闭线程池
            pool.shutdown();
        }
    }
    

    执行结果:

    image-20231205213557566

    分析:

    有2线程执行,第3个任务超出了最大线程数,所以进入阻塞队列,第4个任务发现了阻塞队列满了,此时最大线程数已经达到最大值了,而阻塞队列也满了,所以执行拒绝策略:丢弃阻塞队列中老任务(也就是第3个任务),将新的任务(第4个任务)添加进去,最后等前面任务执行完,释放线程后,执行了阻塞队列中的任务,也就是任务4被执行了,所以共执行了3个任务,丢弃了一个(任务3)

总结

阿里巴巴编码规范对于线程池的使用有一些规范和建议,以下是一些主要的指导原则:

  1. 线程池基本规范
    • 推荐使用线程池的方式创建线程:使用线程池可以减少线程的创建和销毁开销,提高系统的性能。
    • 使用ThreadPoolExecutor创建线程池:尽量使用ThreadPoolExecutor类创建线程池,以便灵活地配置线程池参数。
  2. 线程池参数配置
    • 避免使用无界队列:无界队列(如LinkedBlockingQueue)可能导致队列无限增长,最终耗尽系统资源。建议使用有界队列来限制队列的长度。
    • 合理配置线程池大小:根据业务场景和系统资源,合理配置核心线程数、最大线程数、存活时间等参数,以优化线程池的性能。
    • 避免使用固定大小的线程池:固定大小的线程池可能在高并发时无法处理大量的请求,建议根据实际需求使用可伸缩的线程池。
  3. 拒绝策略选择
    • 慎重选择拒绝策略:根据实际业务场景,选择合适的拒绝策略。通常情况下,建议使用CallerRunsPolicy,避免直接抛出异常或丢弃任务。
    • 定制拒绝策略:如果默认的拒绝策略无法满足需求,可以实现自定义的拒绝策略。
  4. 避免线程池滥用
    • 谨慎使用线程池:线程池不是万能的,不适合所有场景。在某些特定的业务场景中,可能需要考虑使用其他并发控制手段,如信号量、CountDownLatch 等。
  5. 任务提交方式
    • 使用executesubmit合理execute适用于不需要获取执行结果的场景,而submit适用于需要获取执行结果的场景。
  6. 处理异常
    • 及时处理任务中的异常:对于submit方法提交的任务,需要通过Future.get()来检查任务执行结果,包括异常。如果不及时处理异常,可能导致任务失败而无法及时发现问题。
  • 谨慎使用线程池:线程池不是万能的,不适合所有场景。在某些特定的业务场景中,可能需要考虑使用其他并发控制手段,如信号量、CountDownLatch 等。
  1. 任务提交方式
    • 使用executesubmit合理execute适用于不需要获取执行结果的场景,而submit适用于需要获取执行结果的场景。
  2. 处理异常
    • 及时处理任务中的异常:对于submit方法提交的任务,需要通过Future.get()来检查任务执行结果,包括异常。如果不及时处理异常,可能导致任务失败而无法及时发现问题。

这些规范和建议有助于确保线程池的稳定运行,提高系统的性能和可维护性。在具体应用时,还需根据具体业务场景和需求进行适度调整。

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

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

相关文章

BearPi Std 板从入门到放弃 - 后天篇(2)(I2C1读写EEPROM)

简介 基于 BearPi Std 板从入门到放弃 - 后天篇&#xff08;1&#xff09;(I2C1 读取 光照强度)&#xff0c; 使用同一个I2C接口访问EEPROM, 同时读取光照亮度 主芯片: STM32L431RCT6 LED : PC13 \ 推挽输出即可 \ 高电平点亮 串口: Usart1 I2C : I2C1 光照强度传感器&#xf…

谈一谈C++的类对象的存储方式

在C的类中&#xff0c;有成员变量和成员函数。当类经过实例化后&#xff0c;便有了类对象&#xff0c;C示例对象中的成员变量和成员函数是分开存储的。 成员变量 : 普通成员变量 : 在 对象 指针指向的内存中存储 , 存储方式与 C 语言中的 struct 结构体 存储变量的 内存结布局 …

IDEA插件配置--maven篇

仓库地址 IDEA中maven插件仓库默认地址&#xff1a;C:\Users\Administrator.m2\repository 在D盘新建一个文件夹用做本地仓库地址&#xff0c;例如 D:\Program Files\maven\repository&#xff0c;将原先C盘路径下的repository拷贝到D盘 修改settings.xml配置文件 镜像地…

springboot077基于SpringBoot的汽车票网上预订系统

springboot077基于SpringBoot的汽车票网上预订系统 成品项目已经更新&#xff01;同学们可以打开链接查看&#xff01;需要定做的及时联系我&#xff01;专业团队定做&#xff01;全程包售后&#xff01; 2000套项目视频链接&#xff1a;https://pan.baidu.com/s/1N4L3zMQ9n…

项目经理是干出来的,不是教出来的

大家好&#xff0c;我是老原。 有不少新手项目经理&#xff0c;在通过了PMP认证考试&#xff0c;拿到PMP证书后&#xff0c;对之前无序的项目管理状态感觉有了一丝通透的感觉&#xff0c;对接受新项目更是信心满满。 然后就有不少没有项目管理经验&#xff0c;且刚刚考取PMP证…

FIR IP 学习记录

工具&#xff1a; matlab filterdesigner 工具箱 vivado FIR IP核 实现&#xff1a; 1.matlab设计与测试 先用matlab设计目标滤波器&#xff0c;得到滤波器的抽头系数。 如图&#xff0c;根据需求选择 低通/高通/带通/带阻。 由于vivado用的是FIR IP核&#xff0c;所以设…

更换cmd下默认选择Python解释器

问题 我的电脑里有多个Python解释器&#xff0c;一个是自己下载的python37&#xff0c;版本是3.7.0&#xff0c;一个是anaconda的base环境&#xff0c;版本是3.7.4&#xff0c;还有虚拟环境里的python解释器。 最近发现&#xff0c;在cmd下输入python&#xff0c;使用的是anac…

卡通渲染总结《一》

本文是在看完之前的综述论文《Cartoon Style Rendering》的总结&#xff0c;论文时间是2008年有点早&#xff0c;但有一定启发意义。 前言 首先卡通渲染是非真实化渲染&#xff08;NPR&#xff09;的一个部分.而NPR旨在模拟出手工插图的效果例如油画、墨水画、漫画风格作品。 …

【C++】const关键字的详解!!

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

在机器学习或者深度学习中是否可以直接分为训练集和测试集而不需要验证集?我的答案如下:

文章目录 一、训练集是什么&#xff1f;二、验证集是什么&#xff1f;三、测试集是什么&#xff1f;四、是否可以直接分为训练集和测试集而不需要验证集&#xff1f;总结 在机器学习和深度学习项目中&#xff0c;通常会将数据集划分为三个部分&#xff1a;训练集&#xff0c;验…

个人作品集

个人作品集 封面设计 排版设计 3D建模 Pr剪辑 个人剪辑作品 场景搭建

【ArcGIS Pro微课1000例】0049:根据坐标快速定位(创建点位)的常见方法

文章目录 一、转到XY1. 闪烁位置2. 平移3. 标记位置二、定位1. 坐标定位2. 添加到图形3. 添加至要素类三、添加XY坐标四、创建点要素一、转到XY 举例:经纬度坐标:113.2583286东, 23.1492340北 。 1. 闪烁位置 输入坐标,点击闪烁位置工具,即可在对应的位置出现一个绿色闪烁…

【“C++ 精妙之道:解锁模板奇谭与STL精粹之门“】

【本节目标】 1. 泛型编程 2. 函数模板 3. 类模板 4. 什么是STL 5. STL的版本 6. STL的六大组件 7. STL的重要性 8. 如何学习STL 9.STL的缺陷 1. 泛型编程 如何实现一个通用的交换函数呢&#xff1f; void Swap(int& left, int& right) {int temp left;lef…

Hadoop学习笔记(HDP)-Part.14 安装YARN+MR

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

PRCD-1229 : An attempt to access configuration of database

今天维护oda一体机时&#xff0c;发现无法在grid用户下面关闭数据库实例&#xff0c;如下 ASM1:/home/gridoda0>srvctl stop database -d orcl -o immeidate PRCD-1229 : An attempt to access configuration of database orcl was rejected because its version 11.2.0.4.…

Lombok的踩坑系列之@Builder

背景&#xff1a; Lombok 这个插件大家日常工作中几乎是必备的&#xff0c;几个简单的注解就可以帮助我们减少一大坨get/set方法等&#xff1b;其中Builder注解使用的也很广泛&#xff0c;使用了建造者模式帮助我们构建出个性化的对象&#xff0c;本次踩坑点就在这个地方。 先…

智能优化算法应用:基于混沌博弈算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于混沌博弈算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于混沌博弈算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.混沌博弈算法4.实验参数设定5.算法结果6.参考…

Learning Memory-guided Normality for Anomaly Detection 论文阅读

Learning Memory-guided Normality for Anomaly Detection 摘要1.介绍2.相关工作3.方法3.1网络架构3.1.1 Encoder and decoder3.1.2 Memory 3.2. Training loss3.3. Abnormality score 4.实验5.总结总结&代码复现&#xff1a; 文章信息&#xff1a; 发表于&#xff1a;cvpr…

我最喜欢的白版应用,AI加持的新功能开源!强烈推荐

Excalidraw 把他们的文本到图表的功能开源了 Excalidraw是一个虚拟白板应用&#xff0c;专门用于绘制类似手绘的图表。它提供了一个无限的、基于画布的白板&#xff0c;具有手绘风格&#xff0c;支持多种功能。 之前我分享的&#xff1a;72张PNG&#xff0c;图解机器学习 里面…

Linux的IO模型——非阻塞IO

非阻塞IO就是当用户recvfrom时&#xff0c;如果内核数据没有准备好&#xff0c;那么就直接返回结果&#xff0c;而不是阻塞用户进程&#xff0c;让其一直等待。 我们可以发现&#xff0c;非阻塞IO模型中&#xff0c;用户进程在第一个阶段是非阻塞&#xff0c;第二个阶段是阻塞状…