上一篇博客已经说了,Executors就是一个工具类。他创建线程池时,实际上是通过如下:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
参考:方腾飞
总结下,更简单直白的说:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
如果nThreads=3,则表示,核心线程数和最大线程数都为3,使用该方法创建的线程池阻塞队列的长度为Integer.MAX_VALUE。此时无论加入的任务有多少,线程池将一直只提供3个线程,并且这三个不论空闲与否都将一直保持在线程池中不被销毁。FixedThreadPool线程池适合用于任务密集型且任务量不大。但需要注意的是如果提供的nThreads数过小,有可能导致阻塞队列满,导致任务被Reject
使用该方法创建的线程池,newSingleThreadExecutor内部是使用FinalizableDelegatedExecutorService来创建线程池的,FinalizableDelegatedExecutorService继承自DelegatedExecutorService,这个类是个包装类,将一个ThreadPoolExecutor包装后之暴露出ExecutorService中的接口方法出来,因此newSingleThreadExecutor返回的ExecutorService与newFixedThreadPool(1)是有所不同的,它能够保证ExecutorService不被再次配置。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
使用该方法创建的线程池,表示核心线程数为0,最大线程数为Integer.MAX_VALUE,所以这个线程池中的线程变为空闲状态后,只会被保留60秒,然后就会被回收。SynchronousQueue是不能存储元素的阻塞队列,每一个put操作必须等待一个take操作,否则不能继续添加元素,因此,在向这种线程池提交任务时,如果有空闲线程还在keepAlive,那么直接使用该线程,否则将创建新的线程。该线程池适合用于任务简单耗时短的情景。
该方法与其他方法稍有不同,其内部直接调用ScheduledThreadPoolExecutor的构造方法,ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,其内部实现了定时执行的逻辑。跟newFixedThreadPool挺类似的,只不过这个线程池使用的是DelayedWorkQueue,DelayedWorkQueue是一个优先级队列,基于堆结构,队列的最前面始终是执行时间最靠前的。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
这个方法是JDK8中新增加的方法,该方法的内部其实是创建了一个ForkJoinPool(该线程池是搭配着ForkJoinTask来使用的)。何谓ForkJoin呢?其实就是分治、合并,把大的任务划分成多个小的子任务然后分别运行最终把结果汇总合并。那何谓WorkStealing呢?这是一种调度策略,其中已完成自己任务的工作线程可以从其他线程窃取挂起的任务,而并不是空闲。该线程池是并行执行,默认并行量是处理器核心数,任务会在多个处理器之间划分。篇幅问题,就不展开了,下次单独开篇写这个
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
阿里的Java开发手册上面是不建议使用Executors来创建线程池的,而是建议自定义创建
自定义创建还是有很多问题需要注意的
比如:
//阻塞队列的长度应该根据业务需求做出合理估计。
new ThreadPoolExecutor(5,5,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(1000));
如果这样创建一个线程池是很危险的,很有可能会阻塞队列满,导致添加任务被拒绝。因此要考虑到阻塞队列满的时候的处理对策,可以自己设置RejectedExecutionHandler来处理该问题。
并且如果当corePoolSize=maximumPoolSize时,这个时候指定keepAliveTime参数是没有意义的。这些线程是会被持久保持的
可以将上面改为如下,能够减少阻塞队列满的情况:
new ThreadPoolExecutor(5,50,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(1000));
//最好是自己再定义RejectExceptionHandler
new ThreadPoolExecutor(5,50,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(1000),handler);
这样当核心线程都在工作,并且阻塞队列也满了,这个时候就会创建新的线程来执行队列中的任务。
下面这样的构造方式也是存在问题的:
new ThreadPoolExecutor(5,50,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());
这种构造方式会导致maximumPoolSize基本失效,线程池线程的数量一直是5。
下面这样的构造方式也是存在问题的:
new ThreadPoolExecutor(0, 50, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
这种构造方式会导致corePoolSize和maximumPoolSize基本失效,新添加的任务将直接被加到阻塞队列中,直到队列满了,才会创建新线程,但是当队列满了的时候,可能已经OutOfMemoryException了。也就是你新添加的任务可能根本不会执行。
还有这种:
new ThreadPoolExecutor(5, 100, 60, TimeUnit.SECONDS, new SynchronousQueue<>());
如果对任务的数量,执行时间没有明确的认识,这样创建线程池也是很有可能导致Reject。
比如执行如下一段代码:
ExecutorService executorService =
new ThreadPoolExecutor(5, 100, 0, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
for (long i = 0; i <30; i++) {
executorService.execute(() -> {
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(i);
}
由于每个任务执行时间都过长,这将导致线程池数量立马达到最大值,当添加到第101个任务时,将会抛出异常:
rejected from java.util.concurrent.ThreadPoolExecutor@43556938[Running, pool size = 100, active threads = 100, queued tasks = 0, completed tasks = 0]
总结:
自定义线程池时,应该合理根据业务需求估计任务数量,执行速度,来合理的配置线程池的参数。并且最好自定义RejectedExecutionHandler
参考:
https://www.cnblogs.com/vhua/p/5297587.html
https://juejin.im/entry/5afe36a46fb9a07aa213965b