线程池工厂,就像上面已经介绍的,目的是为了给线程起一个有意义的名字。用起来也非常的简单,只需要实现ThreadFactory
接口即可
public class CustomThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread®;
thread.setName(“我是你们自己定义的线程名称”);
return thread;
}
}
具体的使用就不去废话了。
拒绝策略
线程池有四种默认的拒绝策略,分别为:
-
AbortPolicy:这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现;
-
DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。这玩意不建议使用;
-
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。这玩意不建议使用;
-
CallerRunsPolicy:如果任务添加失败,那么主线程就会自己调用执行器中的 executor 方法来执行该任务。这玩意不建议使用;
也就是说关于线程池的拒绝策略,最好使用默认的。这样能够及时发现异常。如果上面的都不能满足你的需求,你也可以自定义拒绝策略,只需要实现 RejectedExecutionHandler
接口即可
public class CustomRejection implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(“你自己想怎么处理就怎么处理”);
}
}
看到这里,我们再来画一张图来总结和概括下线程池的执行示意图:
详细的执行过程全部在图中说明了。
在 java 中,有两个方法可以将任务提交到线程池,分别是submit
和execute
。
execute 方法
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
void execute(Runnable command);
通过以下代码可知 execute() 方法输入的任务是一个Runnable类的实例。
executorService.execute(()->{
System.out.println(“ThreadPoolDemo.execute”);
});
submit 方法
submit()方法用于提交需要返回值的任务。
Future<?> submit(Runnable task);
线程池会返回一个future类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get() 方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
Future<?> submit = executorService.submit(() -> {
System.out.println(“ThreadPoolDemo.submit”);
});
其实,如果优雅的关闭线程池是一个令人头疼的问题,线程开启是简单的,但是想要停止却不是那么容易的。通常而言, 大部分程序员都是使用 jdk 提供的两个方法来关闭线程池,他们分别是:shutdown
或 shutdownNow
;
通过调用线程池的 shutdown
或 shutdownNow
方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程(PS:中断,仅仅是给线程打上一个标记,并不是代表这个线程停止了,如果线程不响应中断,那么这个标记将毫无作用),所以无法响应中断的任务可能永远无法终止。
但是它们存在一定的区别,shutdownNow
首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown
只是将线程池的状态设置成SHUTDOWN
状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法中的任意一个,isShutdown
方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed
方法会返回 true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown
方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow
方法。
这里推荐使用稳妥的 shutdownNow
来关闭线程池,至于更优雅的方式我会在以后的并发编程设计模式中的两阶段终止模式中会再次详细介绍。
为什么叫合理的参数,那不合理的参数是什么样子的?在我们创建线程池的时候,里面的参数该如何设置才能称之为合理呢?其实这是有一定的依据的,我们先来看一下以下的创建的方式:
ExecutorService executorService = new ThreadPoolExecutor(5,
5,
5,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
r -> {
Thread thread = new Thread®;
thread.setName(“线程池原理讲解”);
return thread;
});
你说他合理不合理?我也不知道,因为我们没有参考的依据,在实际的开发中,我们需要根据任务的性质(IO是否频繁?)来决定我们创建的核心的线程数的大小,实际上可以从以下的一个角度来分析:
-
任务的性质:CPU密集型任务、IO密集型任务和混合型任务;
-
任务的优先级:高、中和低;
-
任务的执行时间:长、中和短;
-
任务的依赖性:是否依赖其他系统资源,如数据库连接;
性质不同的任务可以用不同规模的线程池分开处理。分为CPU密集型和IO密集型。
CPU密集型任务应配置尽可能小的线程,如配置 Ncpu+1
个线程的线程池。(可以通过Runtime.getRuntime().availableProcessors()
来获取CPU物理核数)
IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2*Ncpu
。
混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。
如果这两个任务执行时间相差太大,则没必要进行分解。可以通过 Runtime.getRuntime().availableProcessors()
方法获得当前设备的CPU个数。
优先级不同的任务可以使用优先级队列 PriorityBlockingQueue
来处理。它可以让优先级高的任务先执行(注意:如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行)
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
分享
首先分享一份学习大纲,内容较多,涵盖了互联网行业所有的流行以及核心技术,以截图形式分享:
(亿级流量性能调优实战+一线大厂分布式实战+架构师筑基必备技能+设计思想开源框架解读+性能直线提升架构技术+高效存储让项目性能起飞+分布式扩展到微服务架构…实在是太多了)
其次分享一些技术知识,以截图形式分享一部分:
Tomcat架构解析:
算法训练+高分宝典:
Spring Cloud+Docker微服务实战:
最后分享一波面试资料:
切莫死记硬背,小心面试官直接让你出门右拐
1000道互联网Java面试题:
Java高级架构面试知识整理:
nEwn-1710426928043)]
算法训练+高分宝典:
[外链图片转存中…(img-W7xf4A6W-1710426928043)]
Spring Cloud+Docker微服务实战:
[外链图片转存中…(img-7YSWtbNZ-1710426928044)]
最后分享一波面试资料:
切莫死记硬背,小心面试官直接让你出门右拐
1000道互联网Java面试题:
[外链图片转存中…(img-bBWDp04c-1710426928044)]
Java高级架构面试知识整理:
[外链图片转存中…(img-2K94QHRa-1710426928045)]