无法制作具有大小限制的缓存线程池?

问题

似乎不可能创建一个缓存的线程池,它可以创建的线程数限制。

以下是在标准Java库中实现静态Executors.newCachedThreadPool的方法:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

因此,使用该模板继续创建固定大小的缓存线程池:

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new SynchronusQueue<Runable>());

现在如果你使用它并提交3个任务,一切都会好的。提交任何进一步的任务将导致被拒绝的执行异常。

试试这个:

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runable>());

将导致所有线程按顺序执行。即,线程池永远不会有多个线程来处理你的任务。

这是ThreadPoolExecutor的execute方法中的错误?或者这可能是故意的?还是有其他方式?

编辑:我想要一些与缓存的线程池完全相同的东西(它根据需要创建线程,然后在一些超时后杀死它们)但是它可以创建的线程数量有限制,并且一旦有了它就能够继续排队其他任务达到其线程限制。根据sjlee的回应,这是不可能的。查看ThreadPoolExecutor的execute()方法确实是不可能的。我需要继承ThreadPoolExecutor并覆盖execute(),就像SwingWorker一样,但SwingWorker在其execute()中所做的是一个完整的hack。


#1 热门回答(205 赞)

ThreadPoolExecutor具有以下几个关键行为,你的问题可以通过这些行为来解释。

提交任务时,

  • 如果线程池未达到核心大小,则会创建新线程。
  • 如果已达到核心大小且没有空闲线程,则会对任务进行排队。
  • 如果已达到核心大小,则没有空闲线程,并且队列变满,它会创建新线程(直到达到最大大小)。
  • 如果已达到最大大小,则没有空闲线程,并且队列已满,拒绝策略将启动。

在第一个示例中,请注意SynchronousQueue的大小基本为0.因此,当你达到最大大小(3)时,拒绝策略将启动(#4)。

在第二个示例中,选择的队列是LinkedBlockingQueue,其大小不受限制。因此,你会陷入行为#2。

你不能真正修改缓存类型或固定类型,因为它们的行为几乎完全确定。

如果你想拥有一个有界且动态的线程池,则需要使用正核心大小和最大大小以及有限大小的队列。例如,

new ThreadPoolExecutor(10, // core size
    50, // max size
    10*60, // idle timeout
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<Runnable>(20)); // queue with a size

#2 热门回答(56 赞)

除非我错过了什么,否则原问题的解决方案很简单。以下代码实现了原始海报所描述的所需行为。它将产生最多5个线程来处理无界队列,空闲线程将在60秒后终止。

tp = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<Runnable>());
tp.allowCoreThreadTimeOut(true);

#3 热门回答(7 赞)

有同样的问题。由于没有其他答案将所有问题放在一起,我加入我的:

它现在清楚地写在docs中:如果使用不阻塞的队列(LinkedBlockingQueue),则最大线程设置无效,仅使用核心线程。

所以:

public class MyExecutor extends ThreadPoolExecutor {

    public MyExecutor() {
        super(4, 4, 5,TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        allowCoreThreadTimeOut(true);
    }

    public void setThreads(int n){
        setMaximumPoolSize(Math.max(1, n));
        setCorePoolSize(Math.max(1, n));
    }

}

该执行人具有:

  • 没有最大线程的概念,因为我们正在使用无界队列。这是一件好事,因为这样的队列可能会导致执行程序创建大量非核心额外线程(如果它遵循其通常的策略)。
  • 最大大小为Integer.MAX_VALUE的队列。如果待处理任务的数量超过Integer.MAX_VALUE,Submit()将抛出RejectedExecutionException。不确定我们会先耗尽内存还是会发生这种情况。
  • 有4个核心线程可能。空闲核心线程如果空闲5秒则自动退出。所以,是的,严格按需求threads.Number可以使用setThreads()方法改变。
  • 确保核心线程的最小数量永远不会少于1,否则submit()将拒绝每个任务。由于核心线程需要> =最大线程,因此方法setThreads()也设置最大线程,尽管最大线程设置对于无界队列是无用的。