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

时间:2009-11-25 22:18:06

标签: java multithreading concurrency executorservice threadpoolexecutor

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

以下是在标准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。

14 个答案:

答案 0 :(得分:219)

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

提交任务时,

  1. 如果线程池未达到核心大小,则会创建新线程。
  2. 如果已达到核心大小且没有空闲线程,则会将任务排队。
  3. 如果已达到核心大小,则没有空闲线程,并且队列变满,它会创建新线程(直到达到最大大小)。
  4. 如果已达到最大大小,则没有空闲线程,并且队列已满,拒绝策略将启动。
  5. 在第一个示例中,请注意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
    

    附录:这是一个相当古老的答案,当核心大小为0时,JDK似乎改变了它的行为。自JDK 1.6以来,如果核心大小为0且池正确没有任何线程,ThreadPoolExecutor将添加一个线程来执行该任务。因此,核心大小为0是上述规则的例外。感谢Steve bringing提醒我注意。

答案 1 :(得分:58)

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

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

答案 2 :(得分: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));
    }

}

此执行人有:

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

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

  3. 有4个核心线程可能。空闲核心线程在空闲5秒后自动退出。所以,是的,严格按需求使用线程。可以使用setThreads()方法改变数据。

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

答案 3 :(得分:6)

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

CallerRunsPolicy
AbortPolicy
DiscardPolicy
DiscardOldestPolicy

听起来你想要使用CallerRunsPolicy缓存线程池。

答案 4 :(得分:5)

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

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

这会创建一个以{5}开头的ThreadPoolExecutor,并使用CallerRunsPolicy最多同时运行10个线程来执行。

答案 5 :(得分:3)

根据ThreadPoolExecutor的Javadoc:

  

如果有多个corePoolSize但运行的maximumPoolSize线程少于maxPoolSize,则只有在队列已满时才会创建一个新线程。通过设置corePoolSize和maximumPoolSize,可以创建固定大小的线程池。

(强调我的。)

jitter的回答是你想要的,虽然我的回答你的另一个问题。 :)

答案 6 :(得分:2)

这就是你想要的(至少我猜是这样)。有关解释,请查看Jonathan Feinberg answer

Executors.newFixedThreadPool(int n)

  

创建一个线程池,该线程池重用在共享无界队列中运行的固定数量的线程。在任何时候,最多nThreads线程将是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到线程可用。如果任何线程由于在关闭之前执行期间的故障而终止,则在需要执行后续任务时将使用新的线程。池中的线程将一直存在,直到明确关闭为止。

答案 7 :(得分:2)

还有一个选项。您也可以使用任何其他队列,而不是使用新的SynchronousQueue,但您必须确保其大小为1,这样才会强制执行器服务创建新线程。

答案 8 :(得分:2)

看起来好像没有任何答案实际回答这个问题 - 实际上我看不到这样做的方法 - 即使你从PooledExecutorService继承子类,因为许多方法/属性是私有的,例如使addIfUnderMaximumPoolSize受到保护您可以执行以下操作:

class MyThreadPoolService extends ThreadPoolService {
    public void execute(Runnable run) {
        if (poolSize() == 0) {
            if (addIfUnderMaximumPoolSize(run) != null)
                return;
        }
        super.execute(run);
    }
}

我得到的最接近的是 - 但即使这不是一个很好的解决方案

new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) {
    public void execute(Runnable command) {
        if (getPoolSize() == 0 && getActiveCount() < getMaximumPoolSize()) {        
            super.setCorePoolSize(super.getCorePoolSize() + 1);
        }
        super.execute(command);
    }

    protected void afterExecute(Runnable r, Throwable t) {
         // nothing in the queue
         if (getQueue().isEmpty() && getPoolSize() > min) {
             setCorePoolSize(getCorePoolSize() - 1);
         }
    };
 };

P.S。未经测试以上

答案 9 :(得分:1)

这是另一种解决方案。我认为这个解决方案的行为与你想要的一样(虽然不为这个解决方案感到自豪):

final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    public boolean offer(Runnable o) {
        if (size() > 1)
            return false;
        return super.offer(o);
    };

    public boolean add(Runnable o) {
        if (super.offer(o))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
};

RejectedExecutionHandler handler = new RejectedExecutionHandler() {         
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        queue.add(r);
    }
};

dbThreadExecutor =
        new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, queue, handler);

答案 10 :(得分:0)

  1. 您可以按照 @sjlee

    的建议使用ThreadPoolExecutor

    您可以动态控制池的大小。有关详细信息,请查看此问题:

    Dynamic Thread Pool

    或者

  2. 您可以使用已在java 8中引入的newWorkStealingPool API。

    public static ExecutorService newWorkStealingPool()
    
      

    使用所有可用处理器作为目标并行级别创建工作窃取线程池。

  3. 默认情况下,并行度级别设置为服务器中的CPU核心数。如果您有4个核心CPU服务器,则线程池大小为4.此API返回ForkJoinPool类型的ExecutorService,并允许通过从ForkJoinPool中的繁忙线程中窃取任务来窃取空闲线程。

答案 11 :(得分:0)

问题总结如下:

  

我想要的东西完全类似于缓存的线程池(它会按需创建线程,然后在超时后将其杀死),但是它可以创建的线程数受到限制,并且一旦它能够继续排队其他任务已达到其线程限制。

在指向解决方案之前,我将解释以下解决方案为何不起作用:

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

当达到3的限制时,这不会将任何任务排队,因为SynchronousQueue根据定义不能容纳任何元素。

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

这不会创建多个线程,因为如果队列已满,则ThreadPoolExecutor仅创建超过corePoolSize的线程。但是LinkedBlockingQueue永远不会满。

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

这将不会重用线程,直到达到corePoolSize为止,因为ThreadPoolExecutor会增加线程数,直到达到corePoolSize为止,即使现有线程处于空闲状态。如果您可以忍受这个不利条件,那么这是解决问题的最简单方法。这也是“ Java Concurrency in Practice”(p172上的脚注)中描述的解决方案。

所描述问题的唯一完整解决方案似乎是涉及覆盖队列的offer方法并编写RejectedExecutionHandler的解决方案,如对此问题的答案中所述:How to get the ThreadPoolExecutor to increase threads to max before queueing?

答案 12 :(得分:0)

这适用于Java8 +(及其他)。

     Executor executor = new ThreadPoolExecutor(3, 3, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>()){{allowCoreThreadTimeOut(true);}};

其中3是线程数的限制,而5是空闲线程的超时。

如果您想检查它是否可以自己运行,请执行以下代码:

public static void main(String[] args) throws InterruptedException {
    final int DESIRED_NUMBER_OF_THREADS=3; // limit of number of Threads for the task at a time
    final int DESIRED_THREAD_IDLE_DEATH_TIMEOUT=5; //any idle Thread ends if it remains idle for X seconds

    System.out.println( java.lang.Thread.activeCount() + " threads");
    Executor executor = new ThreadPoolExecutor(DESIRED_NUMBER_OF_THREADS, DESIRED_NUMBER_OF_THREADS, DESIRED_THREAD_IDLE_DEATH_TIMEOUT, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>()) {{allowCoreThreadTimeOut(true);}};

    System.out.println(java.lang.Thread.activeCount() + " threads");

    for (int i = 0; i < 5; i++) {
        final int fi = i;
        executor.execute(() -> waitsout("starting hard thread computation " + fi, "hard thread computation done " + fi,2000));
    }
    System.out.println("If this is UP, it works");

    while (true) {
        System.out.println(
                java.lang.Thread.activeCount() + " threads");
        Thread.sleep(700);
    }

}

static void waitsout(String pre, String post, int timeout) {
    try {
        System.out.println(pre);
        Thread.sleep(timeout);
        System.out.println(post);
    } catch (Exception e) {
    }
}

上面对我来说代码的输出是

1 threads
1 threads
If this is UP, it works
starting hard thread computation 0
4 threads
starting hard thread computation 2
starting hard thread computation 1
4 threads
4 threads
hard thread computation done 2
hard thread computation done 0
hard thread computation done 1
starting hard thread computation 3
starting hard thread computation 4
4 threads
4 threads
4 threads
hard thread computation done 3
hard thread computation done 4
4 threads
4 threads
4 threads
4 threads
3 threads
3 threads
3 threads
1 threads
1 threads

答案 13 :(得分:0)

我推荐使用 Signal 方法

来自SignalExecutors 类:

<块引用>

ThreadPoolExecutor 只会在提供的队列从 offer() 返回 false 时创建一个新线程。这意味着如果你给它一个无界队列,它只会创建 1 个线程,不管队列有多长。但是,如果您绑定队列并提交比线程数更多的可运行对象,则您的任务将被拒绝并引发异常。因此,我们创建了一个队列,如果它不为空,它将始终返回 false 以确保创建新线程。然后,如果任务被拒绝,我们只需将其添加到队列中即可。

    public static ExecutorService newCachedBoundedExecutor(final String name, int minThreads, int maxThreads) {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(minThreads,
            maxThreads,
            30,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>() {
                @Override
                public boolean offer(Runnable runnable) {
                    if (size() > 1 && size() <= maxThreads) {
                        //create new thread
                        return false;
                    } else {
                        return super.offer(runnable);
                    }
                }
            }, new NumberedThreadFactory(name));

    threadPool.setRejectedExecutionHandler((runnable, executor) -> {
        try {
            executor.getQueue().put(runnable);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });

    return threadPool;
}