ThreadPoolExecutor-线程池知多少

2024-03-14 08:58

本文主要是介绍ThreadPoolExecutor-线程池知多少,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ThreadPoolExecutor

创建线程池的目的?

线程池为了避免频繁的创建和销毁所带来的的性能消耗,而建立的一种池化技术,它是把已经创建的线程放入到池中。当有任务来临的时候可以重用已有的线程,无需等待创建的过程,这用有效的提高程序的响应速度。

为什么用ThreadPoolExecutor创建而不是Executors

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的读者更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端:

newFixedThreadPool
newSingleThreadExecutor
newCachedThreadPool

上面三个方法的底层原理都是使用ThreadPoolExecutor

newSingleThreadScheduledExecutor

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

ThreadPoolExecutor构造

/*** Creates a new {@code ThreadPoolExecutor} with the given initial* parameters.** @param corePoolSize the number of threads to keep in the pool, even*        if they are idle, unless {@code allowCoreThreadTimeOut} is set//表示线程池的常驻核心数,如果设置为0,则表示在没有任何任务,销毁线程池;如果大于0,即使没有任务,也会保证线程池的线程数量等于此值。如果设置过小,频繁的创建和销毁,设置过大,浪费系统资源。* @param maximumPoolSize the maximum number of threads to allow in the*        pool必须大于0,大于corePoolSize。此值只有在任务比较多,且不能存放在任务队列时,才能用到。* @param keepAliveTime when the number of threads is greater than*        the core, this is the maximum time that excess idle threads*        will wait for new tasks before terminating.表示线程的存活时间,当线程池空闲时间并且超过了此时间,多余的线程就会销毁到线程池的核心线程数的数量为止。* @param unit the time unit for the {@code keepAliveTime} argumentkeepAliveTime的时间单位* @param workQueue the queue to use for holding tasks before they are*        executed.  This queue will hold only the {@code Runnable}*        tasks submitted by the {@code execute} method.线程池的任务队列,线程池的所有线程都在处理任务的时候,新任务缓存到这里排队等到执行* @param threadFactory the factory to use when the executor*        creates a new thread通常在创建线程池时不指定此参数,会使用默认的线程创建工厂的模式,创建线程。* @param handler the handler to use when execution is blocked*        because the thread bounds and queue capacities are reached线程池的拒绝策略,如果workQueue存储满之后,不能创建新的线程来执行任务,就会使用拒绝策略,属于限流保护机制* @throws IllegalArgumentException if one of the following holds:<br>*         {@code corePoolSize < 0}<br>*         {@code keepAliveTime < 0}<br>*         {@code maximumPoolSize <= 0}<br>*         {@code maximumPoolSize < corePoolSize}* @throws NullPointerException if {@code workQueue}*         or {@code threadFactory} or {@code handler} is null*/
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.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

第六个参数threadFactory默认的线程创建工厂

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {// Executors.defaultThreadFactory() 为默认的线程创建工厂this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}
public static ThreadFactory defaultThreadFactory() {return new DefaultThreadFactory();
}
// 默认的线程创建工厂,需要实现 ThreadFactory 接口
static class DefaultThreadFactory implements ThreadFactory {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;}
}

第七个参数RejectedExecutionHandler handler

public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();// 当前工作的线程数小于核心线程数if (workerCountOf(c) < corePoolSize) {// 创建新的线程执行此任务if (addWorker(command, true))return;c = ctl.get();}// 检查线程池是否处于运行状态,如果是则把任务添加到队列if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 再出检查线程池是否处于运行状态,防止在第一次校验通过后线程池关闭// 如果是非运行状态,则将刚加入队列的任务移除if (! isRunning(recheck) && remove(command))reject(command);// 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)else if (workerCountOf(recheck) == 0)addWorker(null, false); // 新建线程执行任务}// 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败else if (!addWorker(command, false)) // 执行拒绝策略reject(command);
}
addWorker(Runnable firstTask, boolean core)

第一个参数表示,线程应首先运行的任务,如果没有则设置为null

第二个参数,true表示使用corePoolSize作为阀值,false表示,maximumPoolSize作为阀值。

任务使用线程图:

在这里插入图片描述

execute与submit方法的区别

ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10, 10L,TimeUnit.SECONDS, new LinkedBlockingQueue(20));
// execute 使用
executor.execute(new Runnable() {@Overridepublic void run() {System.out.println("Hello, execute.");}
});
// submit 使用
Future<String> future = executor.submit(new Callable<String>() {@Overridepublic String call() throws Exception {System.out.println("Hello, submit.");return "Success";}
});
System.out.println(future.get());

两者都是执行线程池任务的,submit可以接受返回值。submit配合future使用。

submit是继承executorService中的方法,execute是继承Executor中的方法。

线程池的拒绝策略

AbortPolicy,终止策略,线程池会抛出异常并终止执行,他是默认的拒绝策略

CallerRunsPolicy,把任务交给当前线程来执行

DiscardPolicy,忽略此任务(最新的任务)

DiscardOldestPolicy,忽略最早的任务(最先加入队列的任务)

 ThreadPoolExecutor executor=new ThreadPoolExecutor(1,3,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 6; i++) {executor.execute(()->{System.out.println(Thread.currentThread().getName());});}

运行结果

pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-1
pool-1-thread-3
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.example.tangtang.boot.launch.LeetCodeSolution.ThreadExample3$$Lambda$1/1496724653@48533e64 rejected from java.util.concurrent.ThreadPoolExecutor@64a294a6[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 5]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)at com.example.tangtang.boot.launch.LeetCodeSolution.ThreadExample3.main(ThreadExample3.java:9)

分析:采用了AbortPolicy拒绝策略,抛出了异常,队列中可以存储两个任务,最大可以创建三个线程来完成任务(2+3=5),所以第六个来了就会抛出异常。

自定义拒绝策略

ThreadPoolExecutor executor=new ThreadPoolExecutor(1, 3, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("这是自定义的拒绝策略");}});for (int i = 0; i <15; i++) {executor.execute(()->{System.out.println(Thread.currentThread().getName());});}

结果如下:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
这是自定义的拒绝策略
这是自定义的拒绝策略
这是自定义的拒绝策略
这是自定义的拒绝策略
这是自定义的拒绝策略
这是自定义的拒绝策略
这是自定义的拒绝策略
这是自定义的拒绝策略
pool-1-thread-2
pool-1-thread-2
pool-1-thread-3

ThreadPoolExecutor扩展方法

beforeExecute
afterExecute
public class ThreadPoolExtend {public static void main(String[] args) {MyThreadPoolExecutor executor=new MyThreadPoolExecutor(2,4,10, TimeUnit.SECONDS,new LinkedBlockingQueue());for (int i = 0; i < 3; i++) {executor.execute(()->{Thread.currentThread().getName();});}}static class MyThreadPoolExecutor extends ThreadPoolExecutor{private final ThreadLocal<Long> localTime = new ThreadLocal<>();public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}@Overrideprotected void beforeExecute(Thread t, Runnable r) {Long sTime = System.nanoTime();// 开始时间 (单位:纳秒)localTime.set(sTime);System.out.println(String.format("%s | before | time=%s",t.getName(),sTime));super.beforeExecute(t,r);}@Overrideprotected void afterExecute(Runnable r,Throwable t){Long eTime = System.nanoTime();// 开始时间 (单位:纳秒)Long totalTime = eTime - localTime.get(); // 执行总时间System.out.println(String.format("%s | after | time=%s | 耗时:%s 毫秒",Thread.currentThread().getName(),eTime,(totalTime/1000000.0)));super.afterExecute(r,t);}}

pool-1-thread-1 | before | time=38938852458000
pool-1-thread-2 | before | time=38938852540300
pool-1-thread-1 | after | time=38938882198300 | 耗时:29.7403 毫秒
pool-1-thread-2 | after | time=38938882205900 | 耗时:29.6656 毫秒
pool-1-thread-1 | before | time=38938883845000
pool-1-thread-1 | after | time=38938884124100 | 耗时:0.2791 毫秒

这篇关于ThreadPoolExecutor-线程池知多少的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/807894

相关文章

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2

SpringBoot3中使用虚拟线程的完整步骤

《SpringBoot3中使用虚拟线程的完整步骤》在SpringBoot3中使用Java21+的虚拟线程(VirtualThreads)可以显著提升I/O密集型应用的并发能力,这篇文章为大家介绍了详细... 目录1. 环境准备2. 配置虚拟线程方式一:全局启用虚拟线程(Tomcat/Jetty)方式二:异步

如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socket read timed out的问题

《如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socketreadtimedout的问题》:本文主要介绍解决Druid线程... 目录异常信息触发场景找到版本发布更新的说明从版本更新信息可以看到该默认逻辑已经去除总结异常信息触发场景复