Java线程池newCachedThreadPool()与newFixedThreadPool()区别 | Baeldung


当涉及线程池实现时,Java标准库提供了很多选择。在这些实现中,固定线程池和缓存线程池非常普遍。

缓存线程池newCachedThreadPool
Executors.newCachedThreadPool缓存线程池是从零个线程开始,并且可能会增长为具有  Integer.MAX_VALUE个 线程。实际上,缓存线程池的唯一限制是可用的系统资源。
为了更好地管理系统资源,缓存的线程池将删除闲置一分钟的线程。为了更好地管理系统资源,缓存的线程池将删除闲置一分钟的线程。
假设有一个新任务进入。如果队列中有一个空闲线程正在等待,则任务生产者将任务移交给该线程。否则,由于队列始终已满,执行程序将创建一个新线程来处理该任务。
缓存线程池配置会在很短的时间内缓存线程(因此命名),以将其重用于其他任务。因此,当我们处理相当数量的短期任务时,它最有效。 
这里的关键是“合理”和“短暂”。为了澄清这一点,让我们评估一个缓存池不太适合的情况。在这里,我们将提交100万个任务,每个任务需要100微秒的时间来完成:

allable<String> task = () -> {
    long oneHundredMicroSeconds = 100_000;
    long startedAt = System.nanoTime();
    while (System.nanoTime() - startedAt <= oneHundredMicroSeconds);
 
    return "Done";
};
 
var cachedPool = Executors.newCachedThreadPool();
var tasks = IntStream.rangeClosed(1, 1_000_000).mapToObj(i -> task).collect(toList());
var result = cachedPool.invokeAll(tasks);

这将创建许多线程,这些线程会转换为不合理的内存使用,甚至更糟的是,还会有许多CPU上下文切换。这两个异常都会严重损害整体性能。
因此,当执行时间不可预测时(如IO绑定任务),我们应避免使用缓存线程池。

固定线程池newFixedThreadPool
与缓存线程池相反,该线程正在使用具有固定数量的永不过期线程的无界队列。因此,固定线程池不是使用数量不断增加的线程,而是尝试使用固定数量的线程执行传入的任务。当所有线程都忙时,执行程序将对新任务进行排队。这样,我们可以更好地控制程序的资源消耗。
因此,固定线程池更适合执行时间无法预测的任务。

共同点
除了所有这些差异,它们都使用  AbortPolicy 作为饱和策略。因此,我们希望这些执行器在无法接受甚至无法排队任务时抛出异常。
让我们看看现实世界中发生了什么。
在极端情况下,缓存线程池将继续创建越来越多的线程,因此,实际上,它们永远不会达到饱和点。同样,固定线程池将继续在其队列中添加越来越多的任务。因此,固定池也永远不会达到饱和点。
由于两个池都不会饱和,因此当负载异常高时,它们将消耗大量内存来创建线程或排队任务。更糟的是,缓存的线程池也将导致很多处理器上下文切换。
无论如何,为了更好地控制资源消耗,强烈建议创建一个自定义  ThreadPoolExecutor

var boundedQueue = new ArrayBlockingQueue<Runnable>(1000);
new ThreadPoolExecutor(10, 20, 60, SECONDS, boundedQueue, new AbortPolicy());

在这里,我们的线程池最多可以有20个线程,最多只能排队1000个任务。同样,当它不能再接受任何负载时,它只会抛出一个异常。