线程池ThreadPoolExecutor使用简介
一、阿里的代码规范:
【强制】线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规 则,规避资源耗尽的风险。
说明: Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和 SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。也可能导致栈溢出。
2) CachedThreadPool和 ScheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。也可能导致栈溢出。
Integer.MAX_VALUE
java int 类型整数的最大值是(2 的 31 次方) - 1 = 2147483648 - 1 = 2147483647(21亿多) (java中 int类型 4Byte(字节) = 32bit(位))
二、ThreadPoolExecutor简介:
参数 1:corePoolSize
核心线程数,线程池中始终存活的线程数。
参数 2:maximumPoolSize
最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
参数 3:keepAliveTime
最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
参数 4:unit:
单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间 ,参数 keepAliveTime 的时间单位有以下 7 种可选:
· TimeUnit.DAYS:天
· TimeUnit.HOURS:小时
· TimeUnit.MINUTES:分
· TimeUnit.SECONDS:秒
· TimeUnit.MILLISECONDS:毫秒
· TimeUnit.MICROSECONDS:微妙
· TimeUnit.NANOSECONDS:纳秒
参数 5:workQueue
一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型:
· ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
· LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
· SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
· PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
· DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
· LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
· LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
较常用的是 ArrayBlockingQueue,LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关。
参数 6:threadFactory
线程工厂,主要用来创建线程,默认为正常优先级、非守护线程。
参数 7:handler
拒绝策略,拒绝处理任务时的策略,系统提供了 4 种可选:
· AbortPolicy:拒绝并抛出异常。
· CallerRunsPolicy:使用当前调用的线程来执行此任务。重试添加当前的任务,他会自动重复调用execute()方法
· DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。抛弃旧的任务
· DiscardPolicy:忽略并抛弃当前任务。
默认策略为 AbortPolicy。
一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。
当一个任务通过execute(Runnable)方法欲添加到线程池时:
如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
也就是:处理任务的优先级为:
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
三、用法举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | package demo; import java.io.Serializable; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class TestThreadPool2 { private static int produceTaskSleepTime = 2 ; private static int produceTaskMaxNumber = 10 ; public static void main(String[] args) { // 构造一个线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 2 , 4 , 3 , TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>( 3 ), new ThreadPoolExecutor.DiscardOldestPolicy()); for ( int i = 1 ; i <= produceTaskMaxNumber; i++) { try { // 产生一个任务,并将其加入到线程池 String task = "task@ " + i; System.out.println( "put " + task); threadPool.execute( new ThreadPoolTask(task)); // 便于观察,等待一段时间 Thread.sleep(produceTaskSleepTime); } catch (Exception e) { e.printStackTrace(); } } } } /** * 线程池执行的任务 */ class ThreadPoolTask implements Runnable, Serializable { private static final long serialVersionUID = 0 ; private static int consumeTaskSleepTime = 2000 ; // 保存任务所需要的数据 private Object threadPoolTaskData; ThreadPoolTask(Object tasks) { this .threadPoolTaskData = tasks; } public void run() { // 处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句 System.out.println(Thread.currentThread().getName()); System.out.println( "start .." + threadPoolTaskData); try { // //便于观察,等待一段时间 Thread.sleep(consumeTaskSleepTime); } catch (Exception e) { e.printStackTrace(); } threadPoolTaskData = null ; } public Object getTask() { return this .threadPoolTaskData; } } |
说明:
1、在这段程序中,一个任务就是一个Runnable类型的对象,也就是一个ThreadPoolTask类型的对象。
2、一般来说任务除了处理方式外,还需要处理的数据,处理的数据通过构造方法传给任务。
3、在这段程序中,main()方法相当于一个残忍的领导,他派发出许多任务,丢给一个叫 threadPool的任劳任怨的小组来做。
这个小组里面队员至少有两个,如果他们两个忙不过来,任务就被放到任务列表里面。
如果积压的任务过多,多到任务列表都装不下(超过3个)的时候,就雇佣新的队员来帮忙。但是基于成本的考虑,不能雇佣太多的队员,至多只能雇佣 4个。
如果四个队员都在忙时,再有新的任务,这个小组就处理不了了,任务就会被通过一种策略来处理,我们的处理方式是不停的派发,直到接受这个任务为止(更残忍!呵呵)。
因为队员工作是需要成本的,如果工作很闲,闲到 3SECONDS都没有新的任务了,那么有的队员就会被解雇了,但是,为了小组的正常运转,即使工作再闲,小组的队员也不能少于两个。
4、通过调整 produceTaskSleepTime和 consumeTaskSleepTime的大小来实现对派发任务和处理任务的速度的控制,改变这两个值就可以观察不同速率下程序的工作情况。
5、通过调整4中所指的数据,再加上调整任务丢弃策略,换上其他三种策略,就可以看出不同策略下的不同处理方式。
6、对于其他的使用方法,参看jdk的帮助,很容易理解和使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | package demo; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolExecutorTest { private static int queueDeep = 4 ; public void createThreadPool() { /* * 创建线程池,最小线程数为2,最大线程数为4,线程池维护线程的空闲时间为3秒, * 使用队列深度为4的有界队列,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除, * 然后重试执行程序(如果再次失败,则重复此过程),里面已经根据队列深度对任务加载进行了控制。 */ ThreadPoolExecutor tpe = new ThreadPoolExecutor( 2 , 4 , 3 , TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueDeep), new ThreadPoolExecutor.DiscardOldestPolicy()); // 向线程池中添加 10 个任务 for ( int i = 0 ; i < 10 ; i++) { try { Thread.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); } while (getQueueSize(tpe.getQueue()) >= queueDeep) { System.out.println( "队列已满,等3秒再添加任务" ); try { Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); } } TaskThreadPool ttp = new TaskThreadPool(i); System.out.println( "put i:" + i); tpe.execute(ttp); } tpe.shutdown(); } private synchronized int getQueueSize(Queue queue) { return queue.size(); } public static void main(String[] args) { ThreadPoolExecutorTest test = new ThreadPoolExecutorTest(); test.createThreadPool(); } class TaskThreadPool implements Runnable { private int index; public TaskThreadPool( int index) { this .index = index; } public void run() { System.out.println(Thread.currentThread() + " index:" + index); try { Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
2021-07-19 Oracle中字符串截取最全方法总结