如何正确使用线程池

2023-10-31

具体请参考原创:

《Java线程池实现原理及其在美团业务中的实践》

《Java 线程池及参数动态调节详解》

一、为何要使用线程池

  • 降低资源消耗

       线程的创建和销毁会造成一定的时间和空间上的消耗,线程池可以让我们重复利用已创建的线程。

  • 提高响应速度

       线程池已为我们创建好了线程,当任务到达时可以不需要等到线程创建就能立即执行。

  • 提高线程的可管理性

       线程是稀缺资源,不可能无限的创建,使用线程池可以进行统一分配、调优和监控。

  • 提供更多更强大的功能

        线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

二、线程池核心参数及执行原理

1、核心参数

ThreadPoolExecutor(int corePoolSize, 
                   int maximumPoolSize, 
                   long keepAliveTime, 
                   TimeUnit unit, 
                   BlockingQueue<Runnable> workQueue, 
                   ThreadFactory threadFactory, 
                   RejectedExecutionHandler handler)

参数

说明

corePoolSize

核心线程数量,线程池维护线程的最少数量

maximumPoolSize

线程池维护线程的最大数量

keepAliveTime

非核心线程的最长空闲时间,超过该时间的空闲线程会被销毁

unit

keepAliveTime的单位,有NANOSECONDS(纳秒)、MICROSECONDS(微秒)、MILLISECONDS(毫秒)、SECONDS(秒)

workQueue

任务缓冲队列(阻塞队列)

threadFactory

线程工厂,用于创建线程,一般用默认的即可

handle

线程池对拒绝任务的处理策略

阻塞队列:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。

  1.  ArrayBlockingQueue:有界、数组结构、FIFO
  2. LinkedBlockingQueue:有界、单链表结构、FIFO、默认长度Integer.MAX_VALUE
  3. SynchronousQueue:不存储元素、每个put操作必须等待take操作,否则阻塞状态
  4. PriorityBlockingQuene:无界、数组的平衡二叉堆、支持线程优先级排序、默认自然序、同优先级不能保证顺序
  5. DelayQueue:无界、基于PriorityBlockingQuene、以时间作为比较基准的优先级队列,这个时间即延迟时间

ThreadPoolExecutor提供了四种拒绝策略:

  1. AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认
  2. CallerRunsPolicy:由调用线程处理该任务( 常用
  3. DiscardPolicy:丢弃任务,但是不抛出异常。
  4. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

ThreadPoolExecutor的运行状态:

运行状态

状态描述

RUNNING

能接受新提交的任务、并且也能处理阻塞队列中的任务

SHUTDOWN

关闭状态,不再接受新提交的任务,但可以继续处理阻塞队列中已保存的任务

STOP

不能接受新提及的任务,也不处理队列中的任务,会中断正在处理任务的线程

TIDYING

所有的任务都已经终止了,workerCount(有效线程数)为0

TERMINATED

在terminated()方法执行完后进入该状态

2、执行原理

三、线程池的创建和使用

1、线程池的创建方式

(1)通过Executors线程工厂类创建(不推荐

1. Executors.newCachedThreadPool();

public static ExecutorService newCachedThreadPool() { 
    return new ThreadPoolExecutor(0, 
                                  Integer.MAX_VALUE, 
                                  60L, 
                                  TimeUnit.SECONDS, 
                                  new SynchronousQueue<Runnable>());
}

该线程池存在的问题:允许的创建线程数量Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。

2. Executors.newFixedThreadPool(int nThreads);

public static ExecutorService newFixedThreadPool(int nThreads) { 
    return new ThreadPoolExecutor(nThreads, 
                                  nThreads, 
                                  0L, 
                                  TimeUnit.MILLISECONDS, 
                                  new LinkedBlockingQueue<Runnable>());
} 

该线程池存在的问题:允许的请求队列长度Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

3. Executors.newSingleThreadExecutor();

public static ExecutorService newSingleThreadExecutor() { 
    return new FinalizableDelegatedExecutorService (
                 new ThreadPoolExecutor(1, 
                                        1,                                   
                                        0L,                                    
                                        TimeUnit.MILLISECONDS, 
                                        new LinkedBlockingQueue<Runnable>())); 
} 

该线程池存在的问题:允许的请求队列长度 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

4. Executors.newScheduledThreadPool(int corePoolSize);

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { 
    return new ScheduledThreadPoolExecutor(corePoolSize); 
} 


public ScheduledThreadPoolExecutor(int corePoolSize) { 
    super(corePoolSize, 
          Integer.MAX_VALUE, 
          0, 
          NANOSECONDS, 
          new DelayedWorkQueue()); 
}

该线程池存在的问题:允许的创建线程数量Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。

(2)通过new ThreadPoolExecutor自定义创建(推荐

ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 
                                                 20, 
                                                 60, 
                                                 TimeUnit.SECONDS, 
                                                 new LinkedBlockingQueue<>(200)); 

2、线程池使用规范(阿里巴巴)

  1. 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
  2. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 自行创建线程,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题。
  3. 线程池不允许使用 Executors工厂类 去创建,而是通过new ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

3、SpringBoot项目中使用线程池

(1)配置线程池并开启异步任务

@Configuration 
@EnableAsync // 开启异步任务支持 
public class ExecutorConfig { 
    
    // 声明线程池 
    @Bean("taskExecutor") 
    public Executor taskExecutor() { 
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 
        executor.setCorePoolSize(5); 
        executor.setMaxPoolSize(20); 
        executor.setQueueCapacity(2000); 
        executor.setThreadNamePrefix("taskExecutor-"); 
        executor.setRejectedExecutionHandler(new 
                           ThreadPoolExecutor.CallerRunsPolicy()); 
        //执行初始化 
        executor.initialize(); 
        return executor; 
    }
    
}

(2)在@Async中使用自定义线程池

@Service 
public class TaskService { 
    
    // @Async声明方法为异步方法并自定使用自定义线程池 
    @Async("taskExecutor") 
    public void task1() { 
        // 具体业务 
    } 
    
} 

(3)@Async失效(本质是代理没有生效)

  1. 异步方法使用了static修饰
  2. 异步方法所在类没有使用@Component或@Service注解进行注释,导致spring无法扫描到异步类
  3. 异步方法类应使用@Autowired或@Resource等注解自动注入到使用类中,不能自己手动new对象
  4. 没有在启动类或配置类中增加@EnableAsync注解启动异步任务支持
  5. 异步方法不能由本类内其他方法调用,必须是外部使用者调用,如果内部方法调用会出现代理绕过的问题,会变成同步操作
  6. 需要继续补充

四、合理配置线程池参数&线程池参数动态配置&线程池监控

具体参考:

《Java线程池实现原理及其在美团业务中的实践》

《Java 线程池及参数动态调节详解》

1、合理配置线程池参数(并没用通用的计算方式

业界的一些线程池参数配置方案:

2、线程池参数动态配置&线程池监控 (美团业务中的方案

线程池参数动态化前后的参数修改流程对比:

线程池参数动态化整体架构:

线程池参数动态化功能架构:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何正确使用线程池 的相关文章

随机推荐

  • 计网第五章(运输层)(六)(TCP可靠传输的实现)

    目录 一 基本概述 二 具体实现 1 前后沿 2 利用指针描述发送窗口的状态 3 有差错情况 之前在数据链路层时已经讨论过可靠传输 计网第三章 数据链路层 二 可靠传输 也在其中提到过可靠传输并不局限于数据链路层 一 基本概述 TCP通过以
  • Batch Normalization详解(原理+实验分析)

    Batch Normalization详解 原理 实验分析 1 计算过程 2 前向传播过程 3 反向传播过程 4 实验分析 4 1 实验一 验证有没有BatchNorm下准确率的区别 4 2 实验二 验证有没有BatchNorm noisy
  • VSCode+Python, conda激活环境错误的解决方案: CommandNotFoundError

    之前VSCode运行Python总是会在终端报错 CommandNotFoundError Your shell has not been properly configured to use conda activate 原因在于每次都会
  • 幼儿园观察记录的目的和目标_幼儿园观察记录:如何让幼儿在区角活动中真正动起来...

    区角活动是幼儿园教育教学活动中的重要组成部分 它贯穿于幼儿园一日生活的各个环节当中 它是幼儿园教育中促进幼儿个性化发展的有效形式 区角活动的内容与主题结合提升目的性 区角活动的材料与本土资源结合 凸显本土性 季节性 可变性 富有特色 教师在
  • mysql 2分钟_java程序mysql数据库每隔2分钟左右就会中断,必须重启tomcat

    原标题 java程序mysql数据库每隔2分钟左右就会中断 必须重启tomcat 解决方案一 推荐 1 问题解决方案及说明 https blog csdn net liuyangvoid article details 25975157 出
  • AIGC从入门到精通

    目录 1 概述 2 一键起飞 3 保持ID生成 4 教程 原理 训练阶段 采样阶段 5 大模型微调 6 训练 7 商业价值 Fooocus sd webui 界面 新手建议使用 ComfyUI 简体中文版 1 概述 Stable Diffu
  • [1065]impala查询内存限制Memory limit exceeded

    错误信息 ERROR Memory limit exceeded Query did not have enough memory to get the minimum required buffers in the block manag
  • Python3发送邮件

    coding utf 8 author ChenBaijing date 2022 4 7 14 33 Python对SMTP支持有smtplib和email两个模块 smtplib负责 登录邮件服务器 认证 和 发送邮件 对smtp协议进
  • vmware虚拟机开机黑屏的解决方法

    今天有一个项目需要用到OSX坯境 打开vmware 启动原来安装的 OSX10 11 结果启动后 无轮怎么操作就是黑屏 然后就百度了一下vmware黑屏的解决办法 尝试了一下问题解决 同时也把解决过程记录一下 1 挂起的时候我们是能够见到显
  • IPSec/GRE与PPTP的比较

    PPTP PPTP Point to Point Tunneling Protocol 是点对点的协议 基于拨号使用的PPP协议使用PAP或CHAP之类的加密算法 或者使用Microsoft的点对点加密算法MPPE 而L2TP Layer
  • Spring Boot + Mybatis 实现动态数据源

    动态数据源 在很多具体应用场景的时候 我们需要用到动态数据源的情况 比如多租户的场景 系统登录时需要根据用户信息切换到用户对应的数据库 又比如业务A要访问A数据库 业务B要访问B数据库等 都可以使用动态数据源方案进行解决 接下来 我们就来讲
  • Ultra Libraian介绍

    Ultra Libraian介绍 我从Ultra Libraian官网上查找一些信息 在下面统一介绍一下它 方便大家使用 Ultra Libraian简介 Ultra Libraian是一个零件库服务商 提供方便电子工程师和PCB Layo
  • 边界值法中的上点、内点和离点分析

    1 边界值法概念 边界值法设计测试用例 是对输入或输出的边界值 有效等价类和无效等价类的界限 进行测试的一种黑盒测试方法 2 边界值法存在的意义 测试经验表明错误往往会发生在输入或输出范围的边界上 所以边界值法是对这些边界进行测试 是对划分
  • 多系统U盘启动盘的制作,成功启动win8PE,ubuntu,deepin2013,deepin2014,以及通过U盘启动电脑已装系统。

    以前的用U盘装系统都是用ultraISO 直接制作启动盘 有的时候一连着好几天都得捣鼓着装系统 今天是windows 明天是ubuntu 后天就可能是其它linux发行版了 很不方便 所以就想利用一个U盘做一个多系统的启动盘 经过N天不断的
  • Linux安装完mysql远程连接Authentication plugin

    root iZ2ze8bpfv23icsz3g2hp2Z log mysql u root p Enter password Welcome to the MySQL monitor Commands end with or g Your
  • MATLAB 学习笔记(6)MATLAB 的 upsample 函数和 downsample 函数

    目录 upsample 和 downsample 程序验证 上采样 upsample 下采样 downsample 其他参数设定 总结 upsample 和 downsample upsample 和 downsample 顾名思义就是上采
  • Concis组件库封装——Loading加载中

    您好 如果喜欢我的文章 可以关注我的公众号 量子前端 将不定期关注推送前端好文 组件介绍 Loading组件是日常开发用的很多的组件 这次封装主要包含两种状态的Loading 旋转 省略号 话不多说先看一下组件的文档页面吧 Loading
  • Docker--简介与实践

    一 什么是容器技术 Docker 是一个开源的应用容器引擎 要了解Docker的前提就是要了解什么是容器技术 说到容器技术 这里就要联系一下我们经常使用到的虚拟机中的虚拟化技术了 两者在功能 目的上相似 都是将一系列的程序进行打包 建立一个
  • [系统安全] 四十二.Powershell恶意代码检测系列 (4)论文总结及抽象语法树(AST)提取

    您可能之前看到过我写的类似文章 为什么还要重复撰写呢 只是想更好地帮助初学者了解病毒逆向分析和系统安全 更加成体系且不破坏之前的系列 因此 我重新开设了这个专栏 准备系统整理和深入学习系统安全 逆向分析和恶意代码检测 系统安全 系列文章会更
  • 如何正确使用线程池

    具体请参考原创 Java线程池实现原理及其在美团业务中的实践 Java 线程池及参数动态调节详解 一 为何要使用线程池 降低资源消耗 线程的创建和销毁会造成一定的时间和空间上的消耗 线程池可以让我们重复利用已创建的线程 提高响应速度 线程池