别告诉我你连线程池都不会用~ 一文搞懂线程池

本文详细介绍了Java线程池的工作原理,包括线程池的五种状态,如RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED,以及Executors工具类的局限性。此外,讲解了ThreadPoolExecutor的七大参数、四种拒绝策略,以及如何根据业务场景选择和配置线程池。还讨论了最佳实践,如处理线程异常和使用CompletableFuture与@Async进行线程池管理。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行

线程池5种状态

=======

  • RUNNING:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。

  • SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。

  • STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。

  • TIDYING

  • SHUTDOWN 状态下,任务数为0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理。

  • 线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态。

  • 线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态。

  • TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态。

image.png

Excutes

=======

  • newFixedThreadPool

  • 创建一个固定的长度的线程池,每当提交一个任务就创建一个线程,知道达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会创建一个新的线程继续运行队列里的任务。

  • newSingleThreadExecutor

  • 这是一个单线程的 Executor ,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来代替它;它的特点是能确保依照任务在队列中的顺序来串行执行。

  • newCachedThreadPool

  • 创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求正驾驶,则可以自动添加新线程,线程池的规模不存在任何限制。

  • newScheduledThreadPool

  • 创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于 Timer

  • newSingleThreadScheduledExecutor

  • 单线程可执行周期性任务的线程池

  • newWorkStealingPool

  • 任务窃取线程池,不保证执行顺序,适合任务耗时差异较大。线程池中有多个线程队列,有的线程队列中有大量的比较耗时的任务堆积,而有的线程队列却是空的,就存在有的线程处于饥饿状态,当一个线程处于饥饿状态时,它就会去其它的线程队列中窃取任务。解决饥饿导致的效率问题。默认创建的并行 level 是 CPU 的核数。主线程结束,即使线程池有任务也会立即停止。

为什么不推荐Executors


Executors工具类创建的线程池队列或线程默认为Integer.MAX_VALUE,容易堆积请求 阿里巴巴Java开发手册:

  • FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM

  • CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM

推荐使用ThreadPoolExecutor类根据实际需要自定义创建

ThreadPoolExecutor

==================

七大参数


ThreadPoolExecutor类主要有以下七个参数:

  • int corePoolSize: 核心线程池大小,线程池中常驻线程的最大数量

  • int maximumPoolSize: 最大核心线程池大小(包括核心线程和非核心线程)

  • long keepAliveTime: 线程空闲后的存活时间(仅适用于非核心线程)

  • TimeUnit unit: 超时单位

  • BlockingQueue<Runnable> workQueue: 阻塞队列

  • ThreadFactory threadFactory: 线程工厂:创建线程的,一般默认

  • RejectedExecutionHandler handle: 拒绝策略

四大策略


拒绝策略就是当队列满时,线程如何去处理新来的任务。

AbortPolicy(中止策略)-默认

  • 功能:当触发拒绝策略时,直接抛出拒绝执行的异常

  • 使用场景:ThreadPoolExecutor中默认的策略就是AbortPolicy,由于ExecutorService接口的系列ThreadPoolExecutor都没有显示的设置拒绝策略,所以默认的都是这个。

CallerRunsPolicy(调用者运行策略)

  • 功能:只要线程池没有关闭,就由提交任务的当前线程处理

  • 使用场景:一般在不允许失败、对性能要求不高、并发量较小的场景下使用。

DiscardPolicy(丢弃策略)

  • 功能:直接丢弃这个任务,不触发任何动作

  • 使用场景: 提交的任务无关紧要,一般用的少。

DiscardOldestPolicy(弃老策略)

  • 功能:抛弃下一个将要被执行的任务,相当于排队的时候把第一个人打死,然后自己代替

  • 使用场景:发布消息、修改消息类似场景。当老消息还未执行,此时新的消息又来了,这时未执行的消息的版本比现在提交的消息版本要低就可以被丢弃了。

工作队列


  • ArrayBlockingQueue:使用数组实现的有界阻塞队列,特性先进先出

  • LinkedBlockingQueue:使用链表实现的阻塞队列,特性先进先出,可以设置其容量,默认为Interger.MAX_VALUE

  • PriorityBlockingQueue:使用平衡二叉树堆,实现的具有优先级的无界阻塞队列

  • DelayQueue:无界阻塞延迟队列,队列中每个元素均有过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最块要过期的元素。

  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态

运行流程

====

  • 判断线程池里的核心线程是否都在执行任务?

  • 否:调用/创建一个新的核心线程来执行任务

  • 是:工作队列是否已满?

  • 否:将新提交的任务存储在工作队列里

  • 是:线程池里的线程数是否达到最大线程值?

  • 否:调用/创建一个新的非核心线程来执行任务

  • 是:执行线程池饱和策略

image.png

一个面试题


一个线程池 core 7; max 20 , queue: 50, 100并发进来怎么分配的?

答:先有7个能直接得到执行, 接下来把50个进入队列排队等候, 在多开13个继续执行。 现在 70 个被安排上了。 剩下 30 个默认执行饱和策略。

执行任务

====

  • execute提交没有返回值,不能判断是否执行成功。只能提交一个Runnable的对象

  • submit会返回一个Future对象,通过Future的get()方法来获取返回值,submit提交线程可以吃掉线程中产生的异常,达到线程复用。当get()执行结果时异常才会抛出。原因是通过submit提交的线程,当发生异常时,会将异常保存,待future.get()时才会抛出。

关闭线程池

=====

  • shutdown():不再继续接收新的任务,执行完成已有任务后关闭

  • shutdownNow():直接关闭,若果有任务尝试停止

线程池出现异常会发生什么?

=============

  • 线程出现异常,线程会退出,并重新创建新的线程执行队列里任务,不能复用线程

  • 当业务代码的异常捕获了,线程就可以复用

  • 使用ThreadFactory的UncaughtExceptionHandler保证线程的所有异常都能捕获(包括业务的异常),兜底的.如果提交方式用execute,不能复用线程

  • setUncaughtExceptionHandler+submit :可以吃掉异常并复用线程(是吃掉,不报错)

  • setUncaughtExceptionHandler+submit+future.get() :可以获取到异常并复用线程

最佳实践


  1. 提交线程的业务异常try catch处理,保证线程不会异常退出

  2. 业务之外的异常我们不可预见的,创建线程池设置ThreadFactory的UncaughtExceptionHandler可以对未捕获的异常做保底处理,通过submit提交任务,可以吃掉异常并复用线程;想要捕获异常这时用future.get()

注:关于异常处理的相关案例,已在源码中,这里不做展示

实战1:结合CompletableFuture使用线程池

============================

  • CompletableFuture,结合了Future的优点,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。

  • CompletableFuture可以传入自定义线程池,否则使用自己默认的线程池,我们习惯做法是自定义线程池,控制整个项目的线程数量,不使用自定义的线程池,做到可控可调

步骤1:声明一个线程池bean


application.properties

//尽量做到每个业务使用自己配置的线程池

service1.thread.coreSize=10

service1.thread.maxSize=100

service1.thread.keepAliveTime=10

复制代码

线程池属性类

/**

  • @Description: 线程池属性

  • @Author: jianweil

  • @date: 2021/12/9 10:44

*/

@ConfigurationProperties(prefix = “service1.thread”)

@Data

public class ThreadPoolConfigProperties {

private Integer coreSize;

private Integer maxSize;

private Integer keepAliveTime;

}

复制代码

线程池配置类

/**

  • @Description: 线程池配置类:根据不同业务定义不同的线程池配置

**/

@EnableConfigurationProperties(ThreadPoolConfigProperties.class)

@Configuration

public class MyService1ThreadConfig {

@Bean

public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {

return new ThreadPoolExecutor(

pool.getCoreSize(),

pool.getMaxSize(),

pool.getKeepAliveTime(),

TimeUnit.SECONDS,

new LinkedBlockingDeque<>(100000),

Executors.defaultThreadFactory(),

new ThreadPoolExecutor.AbortPolicy()

);

}

}

复制代码

步骤2:使用


注:本文所有源码已分享github

/**

  • @Description: 测试CompletableFuture

  • @Author: jianweil

  • @date: 2021/12/9 10:50

*/

@SpringBootTest

public class CompletableFutureTest {

@Autowired

private ThreadPoolExecutor threadPoolExecutor;

/***

  • 无返回值

  • runAsync

*/

@Test

public void main1() {

System.out.println(“main…start…”);

CompletableFuture.runAsync(() -> {

System.out.println(“当前线程:” + Thread.currentThread().getId());

int i = 10 / 2;

System.out.println(“运行结果:” + i);

}, threadPoolExecutor);

System.out.println(“main…end…”);

}

}

复制代码

实战2:结合@Async使用线程池

=================

  • 在现实的互联网项目开发中,针对高并发的请求,一般的做法是高并发接口单独线程池隔离处理。可能为某一高并发的接口单独一个线程池

方式1:默认线程池


  • 使用@Async注解,在默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池

  • 使用此线程池无法实现线程重用,每次调用都会新建一条线程。若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误

步骤1:自定义一个能查看线程池参数的类

  • 不清楚线程池当时的情况,有多少线程在执行,多少在队列中等待呢?

  • 创建了一个ThreadPoolTaskExecutor的子类,在每次提交线程任务的时候都会将当前线程池的运行状况打印出来

public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);

private void showThreadPoolInfo(String prefix) {

ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

if (null == threadPoolExecutor) {

return;

}

logger.info(“{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]”,

this.getThreadNamePrefix(),

prefix,

threadPoolExecutor.getTaskCount(),

threadPoolExecutor.getCompletedTaskCount(),

threadPoolExecutor.getActiveCount(),

threadPoolExecutor.getQueue().size());

}

@Override

public void execute(Runnable task) {

showThreadPoolInfo(“1. do execute”);

super.execute(task);

}

@Override

public void execute(Runnable task, long startTimeout) {

showThreadPoolInfo(“2. do execute”);

super.execute(task, startTimeout);

}

@Override

public Future<?> submit(Runnable task) {

showThreadPoolInfo(“1. do submit”);

return super.submit(task);

}

@Override

public Future submit(Callable task) {

showThreadPoolInfo(“2. do submit”);

return super.submit(task);

}

@Override

public ListenableFuture<?> submitListenable(Runnable task) {

showThreadPoolInfo(“1. do submitListenable”);

return super.submitListenable(task);

}

@Override

public ListenableFuture submitListenable(Callable task) {

showThreadPoolInfo(“2. do submitListenable”);

return super.submitListenable(task);

}

}

复制代码

步骤2:实现AsyncConfigurer类

  • 要配置默认的线程池,要实现AsyncConfigurer类的两个方法

  • 不需要打印运行状况的可以使用ThreadPoolTaskExecutor类构建线程池

/**

  • @Description: 注解@async配置

  • @Author: jianweil

  • @date: 2021/12/9 11:52

*/

@Slf4j

@EnableAsync

@Configuration

public class AsyncThreadConfig implements AsyncConfigurer {

/**

  • 定义@Async默认的线程池

  • ThreadPoolTaskExecutor不是完全被IOC容器管理的bean,可以在方法上加上@Bean注解交给容器管理,这样可以将taskExecutor.initialize()方法调用去掉,容器会自动调用

  • @return

*/

@Override

public Executor getAsyncExecutor() {

int processors = Runtime.getRuntime().availableProcessors();

//常用的执行器

//ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

//可以查看线程池参数的自定义执行器

ThreadPoolTaskExecutor taskExecutor = new VisiableThreadPoolTaskExecutor();

//核心线程数

taskExecutor.setCorePoolSize(1);

taskExecutor.setMaxPoolSize(2);

//线程队列最大线程数,默认:50

taskExecutor.setQueueCapacity(50);

//线程名称前缀

taskExecutor.setThreadNamePrefix(“default-ljw-”);

taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

//执行初始化(重要)

taskExecutor.initialize();

return taskExecutor;

}

/**

  • 异步方法执行的过程中抛出的异常捕获

  • @return

*/

@Override

public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {

return (ex, method, params) ->

log.error(“线程池执行任务发送未知错误,执行方法:{}”, method.getName(), ex.getMessage());

}

}

复制代码

步骤3: 使用

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档

祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!

感谢大家的支持!!

image.png

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-S3uVeSI0-1713548800693)]

[外链图片转存中…(img-S8WHjaM8-1713548800694)]

[外链图片转存中…(img-aEGdAlNg-1713548800694)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档

祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!

感谢大家的支持!!

[外链图片转存中…(img-oaSQvrUm-1713548800695)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值