专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java线程池newCachedThreadPool和newFixedThreadPool对比

temp10 2025-01-01 22:47:08 java教程 12 ℃ 0 评论

概述

谈到线程池, Java中提供了很多选择。其中, cached thread pool 和 thread thread pool 是最常用到的两个。在本教程中,我们将从底层源码,来分析比较他们是如何工作的,以及有什么不同。

Cached Thread Pool

让我们来看看 Java 在调用 Executors.newCachedThreadPool ()时是如何创建缓存线程池的:

Java线程池newCachedThreadPool和newFixedThreadPool对比

public static ExecutorService newCachedThreadPool() {

return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,

new SynchronousQueue<Runnable>());

}

从上面的代码中, 我们看到了newCachedThreadPool是通过SynchronousQueue来实现的,意思就是,假设有一个新的任务,如果队列上有一个空闲线程在等待,则任务生成器将任务移交给该线程。 否则,由于队列总是满的,执行程序将创建一个新线程来处理该任务。

它使用了一种叫做“同步切换”的策略,当且仅当另一个线程能同步接收该任务时,才可以对该项进行排队。 换句话说,SynchronousQueue 不能容纳任何任务。

缓存池从零个线程开始,可能会增长为 Integer.MAX VALUE 线程。 实际上,缓存线程池的唯一限制是可用的系统资源,即系统可打开的线程数。

当然,为了更好的管理资源,缓存的线程池将删除空闲一分钟的线程。

Cached Thread Pool用例

Cached Thread Pool会将线程缓存(因此而得名)一段时间,以便为其他任务重新使用它们。 因此,当我们处理合理数量的短期任务时,该线程池能运行的很好。

这里有两个关键点, 合理数量和短期。为了说明这一点,我们用一段伪代码举个例子:

Callable<String> task = () -> {

long oneHundredMicroSeconds = 100_000;

long startedAt = System.nanoTime();

while (System.nanoTime() - startedAt <= oneHundredMicroSeconds);

return "Done";

};

cachedPool = Executors.newCachedThreadPool();

tasks = IntStream.rangeClosed(1, 1_000_000).mapToObj(i -> task).collect(toList());

result = cachedPool.invokeAll(tasks);

在这里,我们将提交100万个任务,每个任务需要100微秒完成。这将导致大量线程转换为不合理的内存使用,更糟糕的是,大量 CPU 上下文切换。 这两种异常情况都会严重影响整体性能。

因此,当执行时间不可预测时,或者数量比较大的任务时,我们应该避免使用这个线程池。

Fixed Thread Pool

让我们来看一下固定线程池源码中是如何实现的:

public static ExecutorService newFixedThreadPool(int nThreads) {

return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue<Runnable>());

}

这里,固定线程池使用了LinkedBlockingQueue来实现。

与缓存的线程池相反,这个线程池使用的是一个具有固定数量的,没有过期时间的无界队列。 因此,固定线程池会尝试使用固定数量的线程来执行传入的任务,而不是不断增加线程数量。 当所有线程都处于忙碌状态时,执行器将为新任务排队。 这样,我们就可以更好地控制程序的资源消耗。

因此,固定线程池更适合于执行时间不可预测的任务。

cached/fixed Thread Pool 共同的缺点

到目前为止,我们只列举了缓存线程池和固定线程池之间的区别。以及他们适合使用的场景。

但是两者都有一个共同的缺点,当有大量任务时,不管是cached thread pool 通过增加线程数量来处理,还是fixed Thread Pool对新任务进行排队处理,都会消耗大量内存来创建线程或排队任务。 更糟糕的是,缓存的线程池还会导致大量的处理器上下文切换。

所以,为了更好地控制资源消耗,强烈建议创建一个自定义 ThreadPoolExecutor:

boundedQueue = new ArrayBlockingQueue<Runnable>(1000);

new ThreadPoolExecutor(10, 20, 60, SECONDS, boundedQueue, new AbortPolicy());

在这里,我们的线程池可以有多达20个线程,并且只能将多达1000个任务排队。 此外,当它不能接受任何更多的负载时,它将简单地抛出一个异常。

总结

在本教程中,我们了解了 JDK 源代码,看到了不同的 executor 在底层是如何工作的。 然后,我们比较了固定线程池和缓存线程池,以及他们适合的使用场景。最后,我们尝试使用自定义线程池来解决这些线程池由于自身设计问题,导致的资源消耗问题。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表