首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java并发指南: 线程池ThreadPoolTaskExecutor的工作原理解析及避坑

Java并发指南: 线程池ThreadPoolTaskExecutor的工作原理解析及避坑

作者头像
崔认知
发布于 2023-06-20 03:15:18
发布于 2023-06-20 03:15:18
8.1K05
代码可运行
举报
文章被收录于专栏:nobodynobody
运行总次数:5
代码可运行

简介


Java的线程与操作系统的线程一一对应,每创建一个Java线程对象,操作系统会负责创建与之对应的系统线程线程是操作系统中宝贵的资源,创建和销毁非常昂贵且低效。(目前JDK19已经出现了虚拟线程-Virtual Threads 预览版 )。

为了提升性能和方便线程管理,在开发中,我们一般规定必须使用线程池,不允许自己手动创建线程

在微服务场景下,使用线程池时,为了避免链路追踪信息丢失,必须使用经过链路信息封装的线程池,如Spring Cloud 环境下的TraceableExecutorService

【关注公众号:认知科技技术团队】

线程池ThreadPoolTaskExecutor的7大核心参数及解析


创建线程池【必须使用】的一个包含7大核心参数的构造函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
【关注公众号:认知科技技术团队】

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    

1、核心线程数(corePoolSize) 和最大线程数(maximumPoolSize)

线程池根据corePoolSize和maximumPoolSize的值,自动维护线程池中工作线程的数量,大致规则如下:

(1)当向线程池提交任务时,如果当前线程池中工作线程数小于corePoolSize,就会创建一个新线程来执行该任务,即使线程池中其它的工作线程处于空闲状态

如果核心线程提前创建:prestartAllCoreThreads:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.util.concurrent.ThreadPoolExecutor#prestartCoreThread
java.util.concurrent.ThreadPoolExecutor#prestartAllCoreThreads

则直接进入步骤(2)。

(2)当向线程池提交任务时,如果当前线程池中工作线程数大于corePoolSize,但小于maximumPoolSize,则仅当任务工作队列workQueue满时,才会创建一个新线程来执行该任务

(3)corePoolSize和maximumPoolSize的值不仅能在构造函数指定,而且支持线程池运行时动态设值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.util.concurrent.ThreadPoolExecutor#setCorePoolSize
java.util.concurrent.ThreadPoolExecutor#setMaximumPoolSize

2、keepAliveTime和unit ,空闲线程回收策略时间

默认情况下,非核心线程空闲一定的时间后,是需要释放回收的,这个空闲时间是由keepAliveTime和unit共同决定的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
this.keepAliveTime = unit.toNanos(keepAliveTime);

我们也可以根据任务情况动态设值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.util.concurrent.ThreadPoolExecutor#setKeepAliveTime

空闲线程回收策略,默认情况下只针对非核心线程,如果想应用于核心线程,我们必须显示设置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.util.concurrent.ThreadPoolExecutor#allowsCoreThreadTimeOut

线程池中的工作线程运行时,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.util.concurrent.ThreadPoolExecutor.Worker#run

只要第一个任务或者从任务队列中能获取到任务,此工作线程一直运行。keepAliveTime主要应用于从任务队列阻塞超时获取队头任务。

如果此工作线程空闲keepAliveTime,即任务队列阻塞超时keepAliveTime获取队头任务,获取不到任务时候,设置超时标志,下次for循环,根据下面超时策略判断,是否进入for循环再次从任务队列获取任务,或者超时返回NULL,导致此工作线程运行结束,线程被回收。

3、workQueue,任务队列

任务队列workQueue的作用主要是缓存提交的任务。当线程池线程数目超过非核心线程数且不超过最大线程数,提交的任务被缓存在此任务队列中。线程池中的线程会一直从此任务队列消费任务。

避坑:建议使用容量有限的任务队列,防止任务堆积导致OOM的发生。

4、threadFactory线程工厂

主要是在创建线程时设置线程的名字、daemon属性、优先级等属性。良好的线程名字在jstack命令下很好的分析解决问题

java.util.concurrent.Executors.DefaultThreadFactory的默认实现:

建议使用org.apache.commons.lang3.concurrent.BasicThreadFactory.Builder:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


BasicThreadFactory factory = new BasicThreadFactory.Builder()
                .namingPattern("认知科技thread-%d")
                .daemon(true)
                .priority(Thread.MAX_PRIORITY)
                .build();

5、handler-线程池的任务拒绝策略

向线程池提交任务时,当前线程数超过最大线程数maximumPoolSize,此时核心线程都在忙碌,而且任务队列也满了,提交的任务就会被拒绝。当然,如果线程池正处于关闭时,也会被拒绝提交

JUC提供了几种默认实现:

(1)ThreadPoolExecutor.AbortPolicy

如果线程池不设置任务拒绝策略,默认策略为AbortPolicy,通过抛出异常:

RejectedExecutionException拒绝任务提交。

(2)ThreadPoolExecutor.CallerRunsPolicy

提交任务的线程去执行此任务即当前提交任务的线程直接通过调用Runnable.run()执行任务,会阻塞当前线程。

一般情况下使用此策略,要打印日志上报。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
new ThreadPoolExecutor.CallerRunsPolicy() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                        log.info("rejectedExecution ");
                        super.rejectedExecution(r, e);
                    }
                }

使用场景一般为:串行调用改为并行调用,防止线程池设置不够用,退化为串行调用。当有任务被拒绝时,动态调整线程池大小,找到合适的参数。

(3)ThreadPoolExecutor.DiscardPolicy

被拒绝的任务默默地被丢弃,什么都不会去做,连日志都不打印。

此策略在开发中禁止使用。

如果向线程池提交任务后返回Future,使用不用带超时的get方法获取结果,可能永远会被阻塞。

避坑 崔认知,公众号:认知科技技术团队Java并发:FutureTask如何完成多线程并发执行、任务结果的异步获取?以及如何避其坑

(4)ThreadPoolExecutor.DiscardOldestPolicy

丢失任务队列丢弃队头任务,再次尝试向线程池提交任务。

不建议使用此策略。

上述线程池的任务拒绝策略实现,我们一般都需要继承,增加一个打印日志的操作。

任务提交至线程池ThreadPoolTaskExecutor流程


如下图所示:

(1)当向线程池提交任务时,如果当前线程池中工作线程数小于corePoolSize,就会创建一个新线程来执行该任务,即使线程池中其它的工作线程处于空闲状态。

(2)当向线程池提交任务时,如果当前线程池中工作线程数大于corePoolSize,当前任务被存储至任务工作队列workQueue中。

(3)当向线程池提交任务时,如果当前线程池中工作线程数大于corePoolSize,但小于maximumPoolSize,而且任务工作队列workQueue已满,则创建一个新线程来执行该任务。

(4)当向线程池提交任务时,如果当前线程池中工作线程数大于corePoolSize,并且任务工作队列workQueue已满,而且当前线程池中工作线程数大于maximumPoolSize,则执行任务拒绝策略拒绝任务提交。

线程池ThreadPoolTaskExecutor避坑


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 认知科技技术团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
线程池(ThreadPoolExecutor)的七个参数
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSize,如果没有达到的话,则会创建一个新线程来处理这个任务。
酒楼
2024/01/06
8.8K0
站在架构的角度思考线程池的设计和原理
默认情况下,即使核心线程最初只是在新任务需要时才创建和启动的,也可以使用方法 prestartCoreThread()或 prestartAllCoreThreads() 对其进行动态重写。
架构探险之道
2023/03/04
5880
站在架构的角度思考线程池的设计和原理
Java并发-29.线程池
1. 线程池的原理 线程池优点: 降低资源消耗 提高响应速度 提高线程可管理性 线程池流程 判断核心线程池中线程是否都在执行任务,否则创建新工作线程,是则进入下一步 判断工作队列是否已满,否则提交新的任务存储在这个工作队列里,是则进入下一步 判断线程池中的线程是否都处于工作状态,否则创建一个新的工作线程来执行任务,是则交给饱和策略来处理这个任务 ThreadPoolExecutor执行execute方法有4中情况: 当前运行线程少于corePoolSize,创建新线程执行任务(需要全局锁)
悠扬前奏
2019/06/14
3590
多线程-线程池7大参数及其作用
​ <1>第一个参数:corePoolSize线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。这个参数设置非常关键设置过大浪费资源,设置过小导致线程频繁创建或销毁。
鳄鱼儿
2024/05/21
3620
Java线程池七大参数详解和配置
Java线程池(ThreadPoolExecutor)是一种执行器(Executor),用于在一个后台线程中执行任务。线程池的主要目的是减少在创建和销毁线程时所产生的性能开销。以下是线程池的七个关键参数的详解和配置方法:
用户11397231
2024/12/10
1.7K0
线程池使用详解
创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。这2种方式的缺陷就是:在执行完任务之后无法获取执行结果。
leobhao
2022/06/28
5150
线程池使用详解
java 线程池
概述 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机) 线程池使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务,减少创建和销毁线程的次数。 java 中提供了3种实现 ThreadPoolExecutor:标准线程池 ScheduledThreadPoolExecutor:支持延迟
java404
2018/05/18
1.1K0
线程池和队列学习,队列在线程池中的使用,什么是队列阻塞,什么是有界队列「建议收藏」
2,在ExecuorService中提供了newSingleThreadExecutor,newFixedThreadPool,newCacheThreadPool,newScheduledThreadPool四个方法,这四个方法返回的类型是ThreadPoolExecutor。
全栈程序员站长
2022/08/09
3.6K0
线程池和队列学习,队列在线程池中的使用,什么是队列阻塞,什么是有界队列「建议收藏」
Java线程池
线程池的核心实现类,基于ThreadPoolExecutor可以实现满足不同场景的线程池
spilledyear
2020/02/10
1K0
java线程池
一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。 二:线程池 线程池的作用: 线程池作用就是限制系统中执行线程的数量。      根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率
xiangzhihong
2018/01/30
1.2K0
相关推荐
线程池(ThreadPoolExecutor)的七个参数
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验