(十七)java多线程之ThreadPoolExecutor

news/2024/4/19 0:09:11

本人邮箱: <kco1989@qq.com>
欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
代码已经全部托管github有需要的同学自行下载

引言

在之前的例子,我们要创建多个线程处理一批任务的时候.我是通过创建线程数组,或者使用线程集合来管理的.但是这样做不太好,因为这些线程没有被重复利用.所以这里要引入线程池,今天我们就讲线程池执行器ThreadPoolExecutor.

理论

首先我们来看一下它的构造器:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 这个是ThreadPoolExecutor完整的构造器,其他的构造器其实也是在内部调用这个.

  • corePoolSize 核心线程数,线程池保留线程的数量,即使这些线程是空闲.除非设置了allowCoreThreadTimeOut

  • maximumPoolSize 线程池最大允许的线程数.

  • keepAliveTime 当当前的线程数大于核心线程数,那么这些多余的空闲的线程在被终止之前能等待新任务的时间.

  • unit keepAliveTime时间的单位

  • workQueue 这个是用来保留将要执行的工作队列.

  • threadFactory 用于创建新线程的工厂

  • handler 如果工作队列(workQueue)满了,那么这个handler是将会被执行.

ThreadPoolExecutor还有几个可不带threadFactoryhandler惨的构造器,说明java提供了一些默认的配置,让我们看一下.

如果构造不带threadFactory,那么默认使用java.util.concurrent.Executors.DefaultThreadFactory创建出一个新的工厂对象.通过阅读源代码,主要是在创建新的线程的时候修改了线程名为pool-全局线程池递增数编号-thread-当前线程池线程递增编号,让线程改为非守护线程,并设置线程的优先级为NORM_PRIORITY.

ok,再看一下handler有什么默认值.

  • java.util.concurrent.ThreadPoolExecutor.AbortPolicy 这个是默认使用的拒绝策略,如果有要执行的任务队列已满,且还有任务提交,则直接抛出异常信息

  • java.util.concurrent.ThreadPoolExecutor.DiscardPolicy 这个是忽略策略,如果有要执行的任务队列已满,且还有任务提交,则直接忽略掉这个任务,即不抛出异常也不做任何处理.

  • java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy 忽略最早提交的任务.如果有要执行的任务队列已满,此时若还有任务提交且线程池还没有停止,则把队列中最早提交的任务抛弃掉,然后把当前任务加入队列中.

  • java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy 这个是来着不拒策略.如果有要执行的任务队列已满,此时若还有任务提交且线程池还没有停止,则直接运行任务的run方法.

例子 使用默认的拒绝策略AbortPolicy

public class Demo1 {public static void main(String[] args) {BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(10);RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, queue, handler);for (int i = 0; i < 20; i ++){final int temp = i;pool.execute(() -> {System.out.println("客户" + temp + "来了.......");try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}});}pool.shutdown();}
}

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.kco.test17.demo1.Demo1$$Lambda$1/15497079@ca494b rejected from java.util.concurrent.ThreadPoolExecutor@1a4f24f[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at com.kco.test17.demo1.Demo1.main(Demo1.java:16)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
pool-1-thread-1客户0来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-4客户13来了.......
pool-1-thread-2客户3来了.......
pool-1-thread-1客户4来了.......
pool-1-thread-5客户5来了.......
pool-1-thread-3客户6来了.......
pool-1-thread-4客户7来了.......
pool-1-thread-2客户9来了.......
pool-1-thread-1客户8来了.......
pool-1-thread-3客户10来了.......
pool-1-thread-5客户11来了.......
pool-1-thread-4客户12来了.......

从结果看出来,可以看出线程是重复被使用的,而且当执行的任务超过工作队列的容量时,线程确实抛出了异常.

例子2 使用忽略策略 DiscardPolicy

RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();改为 RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
运行结果如下:

pool-1-thread-1客户0来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-4客户13来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-3客户3来了.......
pool-1-thread-4客户4来了.......
pool-1-thread-1客户5来了.......
pool-1-thread-5客户6来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-3客户7来了.......
pool-1-thread-4客户8来了.......
pool-1-thread-5客户9来了.......
pool-1-thread-1客户10来了.......
pool-1-thread-2客户11来了.......
pool-1-thread-4客户12来了.......

现在线程池正确退出了,而且也不抛出异常了,但是超过工作队列容量的任务全部被忽略了.

例子3 使用忽略最早任务策略 DiscardOldestPolicy

RejectedExecutionHandler改为RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();

pool-1-thread-1客户0来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-4客户13来了.......
pool-1-thread-4客户8来了.......
pool-1-thread-1客户11来了.......
pool-1-thread-5客户10来了.......
pool-1-thread-3客户9来了.......
pool-1-thread-2客户12来了.......
pool-1-thread-1客户15来了.......
pool-1-thread-4客户16来了.......
pool-1-thread-5客户17来了.......
pool-1-thread-2客户19来了.......
pool-1-thread-3客户18来了.......

从以上结果,我们可以看出除了客户0客户2刚好是3个核心线程被执行后,客户3客户7直接被忽略掉了.

例子4 使用来着不拒策略 CallerRunsPolicy

同样讲拒绝策略改为RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
运行程序,结果如下:

pool-1-thread-1客户0来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-4客户13来了.......
main客户15来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-2客户3来了.......
pool-1-thread-1客户4来了.......
main客户18来了.......
pool-1-thread-3客户5来了.......
pool-1-thread-4客户7来了.......
pool-1-thread-5客户6来了.......
pool-1-thread-5客户8来了.......
pool-1-thread-1客户9来了.......
pool-1-thread-4客户10来了.......
pool-1-thread-3客户12来了.......
pool-1-thread-2客户11来了.......
pool-1-thread-1客户16来了.......
pool-1-thread-5客户19来了.......
pool-1-thread-3客户17来了.......

结果,我们可以发现所有的任务都被执行,而且竟然还有两个是在主线程执行的.现在明白我之前说的则直接运行任务的run方法的意思了吧,没错是直接调用run方法,而不是开启线程去执行任务.

例子5 使用自定义的拒绝策略

现在我们自己写一个拒绝策略,要求所有的任务都必须被线程池执行,而且都要在线程池中执行.

public class Demo5 {public static void main(String[] args) {BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(10);RejectedExecutionHandler handler = new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {if (!executor.isShutdown()){try {executor.getQueue().put(r);} catch (InterruptedException e) {e.printStackTrace();}}}};ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 0, TimeUnit.SECONDS, queue, handler);for (int i = 0; i < 20; i ++){final int temp = i;pool.execute(() -> {String name = Thread.currentThread().getName();System.out.println(name + "客户" + temp + "来了.......");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}});}pool.shutdown();}
}

运行结果:

pool-1-thread-1客户0来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-4客户13来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-1客户3来了.......
pool-1-thread-3客户4来了.......
pool-1-thread-5客户5来了.......
pool-1-thread-2客户6来了.......
pool-1-thread-4客户7来了.......
pool-1-thread-1客户8来了.......
pool-1-thread-3客户9来了.......
pool-1-thread-5客户10来了.......
pool-1-thread-4客户11来了.......
pool-1-thread-2客户12来了.......
pool-1-thread-1客户15来了.......
pool-1-thread-3客户16来了.......
pool-1-thread-5客户17来了.......
pool-1-thread-4客户19来了.......
pool-1-thread-2客户18来了.......

ok.所有任务都被线程池执行了.而且我们自定义的拒绝策略也很简单,就是让工作队列调用put让其一直等待,直到有可用的容量存放任务.

打赏

如果觉得我的文章写的还过得去的话,有钱就捧个钱场,没钱给我捧个人场(帮我点赞或推荐一下)
微信打赏
支付宝打赏


https://dhexx.cn/news/show-2510103.html

相关文章

“新基建”定义来了 哪些行业最受益?

“新基建”定义来了 哪些行业最受益&#xff1f; 参考链接&#xff1a;“新基建”定义来了 哪些行业最受益&#xff1f;--IT--人民网 (责编&#xff1a;赵超、吕骞)

TCP/IP 运输层

快速导航 网络层与运输层的作用区别&#xff1f; 进程与进程之间如何通信&#xff1f; 什么是封装和解封&#xff1f; 复用和分用&#xff1f; 无连接和面向连接的服务&#xff1f; 简单协议&#xff1f; 停止等待协议&#xff1f; 返回N协议&#xff1f; 选择重传协议&#xf…

Raft指南

2019独角兽企业重金招聘Python工程师标准>>> 在一个分布式环境下&#xff0c;基于读写性能、数据安全等方面的考虑&#xff0c;一份数据往往会有多个副本&#xff0c;如何维护多个副本的一致性&#xff0c;长期以来都是分布式系统中的一个重要而困难的问题。过去10多…

SQL-MySQL使用教程-对MySQL的初步尝试

出现问题&#xff1a;中文无法显示、存储&#xff1b;不对任何数据做检测&#xff0c;只管理数据类型。 转载于:https://www.cnblogs.com/gaosheng-221/p/6717323.html

油电混合是什么意思,插电混动和油电混动的区别?

油电混合是什么意思&#xff0c;插电混动和油电混动的区别&#xff1f; 参考链接&#xff1a;油电混合是什么意思&#xff0c;插电混动和油电混动的区别_车主指南 (icauto.com.cn) 所谓的油电混合一般是指燃料与电能的混合&#xff0c;内燃机与电机的输出特性恰好相反&#x…

C++标准异常类

C中的标准异常类namespace std { //exception派生 class logic_error; //逻辑错误,在程序运行前可以检测出来 //logic_error派生 class domain_error; //违反了前置条件 class invalid_argument; //指出函数的一个无效参数 class length_error; //指出有一个超过类型size_t的最…

《Android游戏开发详解》——第2章,第2.8节对象的基础知识

本节书摘来自异步社区《Android游戏开发详解》一书中的第2章&#xff0c;第2.8节对象的基础知识&#xff0c;作者 【美】Jonathan S. Harbour&#xff0c;更多章节内容可以访问云栖社区“异步社区”公众号查看 2.8 对象的基础知识Android游戏开发详解我们已经应用了第1章中介绍…

《Windows Server 2012 Hyper-V虚拟化管理实践》一1.3 物理服务器选型建议

本节书摘来异步社区《Windows Server 2012 Hyper-V虚拟化管理实践》一书中的第1章&#xff0c;第1.3节&#xff0c;作者&#xff1a; 王淑江 责编&#xff1a; 王峰松&#xff0c;更多章节内容可以访问云栖社区“异步社区”公众号查看。 1.3 物理服务器选型建议 Hyper-V主机是…

3D数学公式

众所周知&#xff0c;数学中向量、矩阵&#xff08;变换&#xff09;、齐次坐标以及四元数的概念和规则是3D游戏编程和计算机图形学的理论基础。下面介绍开发过程中经常用到的一些数学公式。 直线公式 P(t) (1-t)P1 tP2 其中,P1和P2表示3D空间的两点向量。t可以是任意实数。…

理解C++ dynamic_cast

理解C dynamic_cast 在面向对象程序设计中&#xff0c;有时我们需要在运行时查询一个对象是否能作为某种多态类型使用。与Java的instanceof&#xff0c;以及C#的as、is运算符类似&#xff0c;C提供了dynamic_cast函数用于动态转型。相比C风格的强制类型转换和C reinterpret_cas…