前言
最近在业务中需要处理较多并且比较繁重的任务,于是自然就想到了线程池。于是乎,就毫不犹豫地用到ThreadPoolExecutor去自定义线程池,可没想到的是,因为自己的对ThreadPoolExecutor的无知,踩坑了,哎呦,我去(具体是什么坑下面介绍它的原理之后,再娓娓道来,嘻嘻)。
这篇文章主要内容包括两个方面:
- 简单使用
- 原理介绍
简单使用
ThreadPoolExecutor构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
下面对各个参数进行介绍
- corePoolSize:线程池的核心线程数,也就是线程池中会始终保持至少有这么多线程,无论这些线程处于空闲或者工作状态。
- maximumPoolSize:线程池中的最大线程数。
- keepAliveTime:当线程数大于corePoolSize时,其他线程的最大空闲时间,当空闲时间大于这个数值之后,线程就会被销毁。
- unit:keepAliveTime的单位。
- workQueue:一个队列,用于存储执行前的task。
- threadFactory: 线程工厂。
- handler:当所有线程数为maximumPoolSize,且所有线程都处于工作状态时,并且workQueue容量已满时,对任务的拒绝策略。
上面的参数比较简单,这里对handler参数进一步进行说明,它支持的拒绝策略有四种:
// 直接丢弃,并抛出RejectedExecutionException异常
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
// 丢弃任务,不会抛异常
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
// 不丢弃当前job,换而丢弃workQueue中最老的job
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
// 不异步去执行job,当前线程直接同步去执行这个job
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
example
下面是一个简单的使用例子,从线程池的初始化,到执行有结果和无结果的异步任务,最后关闭线程池。
public static void main(String[] args) {
// 申明一个线程工厂,创建线程的名称是test-thread-pool-加number
ThreadFactory threadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber=new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "test-thread-pool-"+threadNumber.incrementAndGet());
}
};
// 使用AbortPolicy拒绝策略
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ExecutorService executorService = new ThreadPoolExecutor(1, 4, 1, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5), threadFactory, handler);
Callable callable = () -> {
System.out.println(Thread.currentThread().getName() + " , I'm callable task");
Thread.sleep(1000);
return "task execute ok!";
};
try {
// 提交一个有返回结果的异步任务
String callableResult = executorService.submit(callable).get();
System.out.println(Thread.currentThread().getName()+ " " + callableResult);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 循环提交10个异步任务
for (int i=0; i<10; ++i) {
try {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
//关闭线程池
executorService.shutdown();
}
内部实现流程
其实内部实现,大家通过参数就可以得知,任务少的时候的线程数只有核心线程数,然后任务多的时候就会额外创建新的线程。但是,怎么去衡量任务多和任务少,具体什么时候任务会直接拿去执行、或者拿去放到队列里边。看到下面的流程图,真相可大白!
其中 workQueue就是存储执行前task的阻塞队列,workQueueSize即该队列的大小。当处理完job完之后的线程,它们还有一些生命周期。,对它们的处理流程如下:
总结来讲,在线程池内部主要分为了三个情况进行处理。
- workSize < corePoolSize时创建线程。
- workSize >= corePoolSize时,将job放到workQueue中。
- 当队列已经满了,则又创建新的线程去处理job,但是得满足当前线程数小于maximumPoolSize,否则进行reject处理。
踩坑经历以及总结
踩坑的原因呢,确实是因为对线程池的不了解。情况就是这么个情况。
我需要去处理一些任务比较耗时,且比较多的任务。于是就想到了线程池去处理,这也是没有问题的。我的参数是:
corePoolSize = 1, maximumPoolSize = 3, workSize = 1000000
这样做错误在于,corePoolSize和maximumPoolSize都设置得过小,线程数根本不够用。当懂得原理之后,就更加的明白,workSize设置得如此之大,当job堆在满之前都是只有一个线程在战斗。
所以,在平时使用中workSize不宜设置得过大,另外还有一个隐患就是workQueue中堆积了这么多job,若发生宕机,job会全部丢掉,这样其实是很不好的,可能发生丢数据或者其他情况。
此外,corePoolSize可以设置为主机核心数,maximumPoolSize设置为corePoolSize的几倍即可。
int corePoolSize = Runtime.getRuntime().availableProcessors()
本文就没有将ThreadPoolExecutor的源代码贴出来了,看图比看代码会直观一些,理解起来更容易。
嘿嘿嘿,就到这里了!