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

Mat*_*ogt 118 java concurrency multithreading executorservice threadpoolexecutor

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

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

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

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

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new SynchronusQueue<Runable>());
Run Code Online (Sandbox Code Playgroud)

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

试试这个:

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runable>());
Run Code Online (Sandbox Code Playgroud)

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

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

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

sjl*_*lee 226

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

提交任务时,

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

在第一个示例中,请注意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
Run Code Online (Sandbox Code Playgroud)

附录:这是一个相当陈旧的答案,看来JDK在核心大小为0时改变了它的行为.从JDK 1.6开始,如果核心大小为0且池没有任何线程,则ThreadPoolExecutor将添加一个线程来执行该任务.因此,核心大小为0是上述规则的例外.感谢史蒂夫使这引起我的注意.

  • 你必须写几个关于方法`allowCoreThreadTimeOut`的文字才能使这个答案变得完美.请参阅@ user1046052的答案 (4认同)

小智 59

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

tp = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<Runnable>());
tp.allowCoreThreadTimeOut(true);
Run Code Online (Sandbox Code Playgroud)

  • 我唯一担心的是(来自JDK 8文档):"当在方法execute(Runnable)中提交新任务时,并且运行的corePoolSize线程少于一个,创建一个新线程来处理请求,即使其他工作者也是如此线程闲置." (4认同)
  • 这确实有效,马特.您将核心大小设置为0,这就是您只有1个线程的原因.这里的技巧是将核心大小设置为最大大小. (2认同)

S.D*_*.D. 7

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

它现在清楚地写在文档中:如果使用不阻塞的队列(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));
    }

}
Run Code Online (Sandbox Code Playgroud)

该执行人具有:

  1. 没有最大线程的概念,因为我们正在使用无界队列.这是一件好事,因为这样的队列可能会导致执行程序创建大量非核心额外线程,如果它遵循其通常的策略.

  2. 最大大小的队列Integer.MAX_VALUE.如果待处理任务的数量超过,Submit()则会抛出.不确定我们会先耗尽内存还是会发生这种情况.RejectedExecutionExceptionInteger.MAX_VALUE

  3. 有4个核心线程可能.闲置核心线程如果空闲5秒则自动退出.所以,是的,严格按需求threads.Number可以使用setThreads()方法改变.

  4. 确保最小数量的核心线程永远不会少于一个,否则submit()将拒绝每个任务.由于核心线程需要> = max个线程,因此该方法也setThreads()设置了最大线程,尽管最大线程设置对于无界队列是无用的.


bri*_*gge 6

在第一个示例中,后续任务被拒绝,因为它AbortPolicy是默认任务RejectedExecutionHandler.ThreadPoolExecutor包含以下策略,您可以通过以下setRejectedExecutionHandler方法更改这些策略:

CallerRunsPolicy
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
Run Code Online (Sandbox Code Playgroud)

听起来你想要一个CallerRunsPolicy的缓存线程池.


小智 5

这里的答案都没有解决我的问题,这与使用Apache的HTTP客户端(3.x版本)创建有限数量的HTTP连接有关.由于我花了几个小时来找出一个好的设置,我将分享:

private ExecutorService executor = new ThreadPoolExecutor(5, 10, 60L,
  TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
  Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
Run Code Online (Sandbox Code Playgroud)

这将创建一个ThreadPoolExecutor以5开头并最多使用10个同时运行的线程来CallerRunsPolicy执行.