上篇博客介绍了线程池参数配置,下面结合案例看一下创建方式
1.原生方式
上图我自己new了一个ThreadPoolExecutor对象,当然ThreadPoolExecutor需要在全局声明,否则会重复创建多个线程池,这里只是测试,所以不必纠结这里的局部声明方式。上面我使用了CompletableFuture去申请调度,然后模拟了一个拒绝策略触发条件。最终的运行结果是主线程阻塞在49行代码。所以这个地方需要注意的是拒绝策略这里,如果真到到了负载阀值,在配合CompletableFuture的时候需要做一些拒绝策略异常抛出,然后try住submit动作,否则这个得不到调度的线程会在allOf join时阻塞整个主线程。
ok,把join换为1.6的实现看看
我把CompletableFuture换为CountDownLatch,同样实现一个多线程调度,主线程join所有task结束的操作。
最终运行结果是最后一个拒绝的task仍然阻塞,但是其他task正常输出。此时代码运行阻塞在了52行最后一次循环。
笔者上周就因为使用了第一种CompletableFuture allOf join操作碰到了线程数超过阀值而未使用submit异常丢弃处理产生了主线程阻塞,整个rpc请求超时引起熔断。这点大家需要在使用过程中注意。
如果大家调大为上述代码中的队列长度或者最大线程数,这个案例将正常打印所有输出,我只是模拟一下峰值扛不住的场景。
当然以上方式,也可以通过spring管理
2.spring方式
只需要跟上图一样将自己配置的ThreadPoolExecutor注入到spring容器,就可以以autowared等方式在其他类中全局引用,或者在静态方法中通过工具类实现ApplicationContextAware接口拿到applicationContext获取bean。
下面看一下Executors和spring线程池
上图已经展示了spring线程池等配置,第一个bean就是spring线程池实现,参数名就可以看出参数含义,所以不一一介绍了。
具体表现大家可以自己测试,实践出真知
最后介绍一下Executors实现
3.Executors方式
Executors实现的线程池就是以上述方式创建,底层就是去构造一个ThreadPoolExecutor构造函数。
但是它内部的实现(比如fix池没有队列长度限制),可能会造成内存泄漏或者cpu太密集,所以建议大家对于线程池理解够深的话,尽量是自己去配置,而不使用这种最简单的方式。
以上代码均在博主的基佬hub中:https://github.com/Spring5945/Concurrent
感兴趣的可以pull下来自己跑跑看,博主比较懒,更新比较慢,但是这不是你们不点star的理由。
下一节主要讲解线程池调优,以及针对cpu密集型和io密集型系统参数选择。
网友评论