简体   繁体   English

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

[英]Impossible to make a cached thread pool with a size limit?

It seems to be impossible to make a cached thread pool with a limit to the number of threads that it can create.似乎不可能制作一个缓存线程池,限制它可以创建的线程数。

Here is how static Executors.newCachedThreadPool is implemented in the standard Java library:以下是标准 Java 库中静态 Executors.newCachedThreadPool 的实现方式:

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

So, using that template to go on to create a fixed sized cached thread pool:因此,使用该模板继续创建一个固定大小的缓存线程池:

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

Now if you use this and submit 3 tasks, everything will be fine.现在,如果您使用它并提交 3 个任务,一切都会好起来的。 Submitting any further tasks will result in rejected execution exceptions.提交任何进一步的任务将导致被拒绝的执行异常。

Trying this:试试这个:

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

Will result in all threads executing sequentially.将导致所有线程顺序执行。 Ie, the thread pool will never make more than one thread to handle your tasks.即,线程池永远不会产生多个线程来处理您的任务。

This is a bug in the execute method of ThreadPoolExecutor?这是ThreadPoolExecutor 的execute 方法中的一个bug? Or maybe this is intentional?或者这可能是故意的? Or there is some other way?或者有其他方法吗?

Edit: I want something exactly like the cached thread pool (it creates threads on demand and then kills them after some timeout) but with a limit on the number of threads that it can create and the ability to continue to queue additional tasks once it has hit its thread limit.编辑:我想要一些与缓存线程池完全一样的东西(它按需创建线程,然后在超时后杀死它们),但它可以创建的线程数量有限制,并且一旦它有继续排队附加任务的能力达到其线程限制。 According to sjlee's response this is impossible.根据 sjlee 的回应,这是不可能的。 Looking at the execute() method of ThreadPoolExecutor it is indeed impossible.看ThreadPoolExecutor的execute()方法确实是不可能的。 I would need to subclass ThreadPoolExecutor and override execute() somewhat like SwingWorker does, but what SwingWorker does in its execute() is a complete hack.我需要子类化 ThreadPoolExecutor 并覆盖 execute() 有点像 SwingWorker 所做的,但 SwingWorker 在其 execute() 中所做的是一个完整的黑客。

The ThreadPoolExecutor has the following several key behaviors, and your problems can be explained by these behaviors. ThreadPoolExecutor 有以下几个关键行为,你的问题可以用这些行为来解释。

When tasks are submitted,提交任务时,

  1. If the thread pool has not reached the core size, it creates new threads.如果线程池未达到核心大小,则创建新线程。
  2. If the core size has been reached and there is no idle threads, it queues tasks.如果已达到核心大小且没有空闲线程,则将任务排队。
  3. If the core size has been reached, there is no idle threads, and the queue becomes full, it creates new threads (until it reaches the max size).如果已达到核心大小,则没有空闲线程,并且队列已满,则创建新线程(直到达到最大大小)。
  4. If the max size has been reached, there is no idle threads, and the queue becomes full, the rejection policy kicks in.如果已达到最大大小,没有空闲线程,并且队列已满,则拒绝策略启动。

In the first example, note that the SynchronousQueue has essentially size of 0. Therefore, the moment you reach the max size (3), the rejection policy kicks in (#4).在第一个示例中,请注意 SynchronousQueue 的大小基本上为 0。因此,当您达到最大大小 (3) 时,拒绝策略就会启动 (#4)。

In the second example, the queue of choice is a LinkedBlockingQueue which has an unlimited size.在第二个示例中,选择的队列是具有无限大小的 LinkedBlockingQueue。 Therefore, you get stuck with behavior #2.因此,您会被行为#2 困住。

You cannot really tinker much with the cached type or the fixed type, as their behavior is almost completely determined.您不能真正修改缓存类型或固定类型,因为它们的行为几乎完全确定。

If you want to have a bounded and dynamic thread pool, you need to use a positive core size and max size combined with a queue of a finite size.如果您想拥有一个有界且动态的线程池,则需要使用正的核心大小和最大大小以及有限大小的队列。 For example,例如,

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

Addendum : this is a fairly old answer, and it appears that JDK changed its behavior when it comes to core size of 0. Since JDK 1.6, if the core size is 0 and the pool does not have any threads, the ThreadPoolExecutor will add a thread to execute that task.附录:这是一个相当老的答案,似乎 JDK 在核心大小为 0 时改变了它的行为。从 JDK 1.6 开始,如果核心大小为 0 并且池没有任何线程,则 ThreadPoolExecutor 将添加一个线程来执行该任务。 Therefore, the core size of 0 is an exception to the rule above.因此,核心大小为 0 是上述规则的一个例外。 Thanks Steve for bringing that to my attention.感谢史蒂夫我注意到这一点。

Unless I've missed something, the solution to the original question is simple.除非我遗漏了什么,否则原始问题的解决方案很简单。 The following code implements the desired behavior as described by the original poster.以下代码实现了原始海报所描述的所需行为。 It will spawn up to 5 threads to work on an unbounded queue and idle threads will terminate after 60 seconds.它将产生多达 5 个线程来处理无界队列,空闲线程将在 60 秒后终止。

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

Had same issue.有同样的问题。 Since no other answer puts all issues together, I'm adding mine:由于没有其他答案可以将所有问题放在一起,因此我添加了我的:

It is now clearly written in docs : If you use a queue that does not blocks ( LinkedBlockingQueue ) max threads setting has no effect, only core threads are used.现在在文档中清楚地写了:如果您使用不阻塞的队列( LinkedBlockingQueue )最大线程设置无效,则仅使用核心线程。

so:所以:

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));
    }

}

This executor has:这个执行者有:

  1. No concept of max threads as we are using an unbounded queue.没有最大线程的概念,因为我们使用的是无界队列。 This is a good thing because such queue may cause executor to create massive number of non-core, extra threads if it follows its usual policy.这是一件好事,因为如果执行器遵循其通常的策略,则此类队列可能会导致执行器创建大量非核心的额外线程。

  2. A queue of max size Integer.MAX_VALUE .最大大小为Integer.MAX_VALUE队列。 Submit() will throw RejectedExecutionException if number of pending tasks exceeds Integer.MAX_VALUE .如果挂起的任务数超过Integer.MAX_VALUE Submit()将抛出RejectedExecutionException Not sure we will run out of memory first or this will happen.不确定我们会先耗尽内存,否则会发生这种情况。

  3. Has 4 core threads possible.可能有 4 个核心线程。 Idle core threads automatically exit if idle for 5 seconds.So, yes, strictly on demand threads.Number can be varied using setThreads() method.如果空闲 5 秒,空闲的核心线程会自动退出。所以,是的,严格按需线程。可以使用setThreads()方法更改setThreads()

  4. Makes sure min number of core threads is never less than one, or else submit() will reject every task.确保核心线程的最小数量永远不会小于 1,否则submit()将拒绝每个任务。 Since core threads need to be >= max threads the method setThreads() sets max threads as well, though max thread setting is useless for an unbounded queue.由于核心线程需要 >= 最大线程数,因此setThreads()方法setThreads()设置了最大线程数,尽管最大线程数设置对于无界队列是无用的。

In your first example, subsequent tasks are rejected because the AbortPolicy is the default RejectedExecutionHandler .在您的第一个示例中,后续任务被拒绝,因为AbortPolicy是默认的RejectedExecutionHandler The ThreadPoolExecutor contains the following policies, which you can change via the setRejectedExecutionHandler method: ThreadPoolExecutor 包含以下策略,您可以通过setRejectedExecutionHandler方法更改这些setRejectedExecutionHandler

CallerRunsPolicy
AbortPolicy
DiscardPolicy
DiscardOldestPolicy

It sounds like you want cached thread pool with a CallerRunsPolicy.听起来您希望使用 CallerRunsPolicy 缓存线程池。

None of the answers here fixed my problem, which had to do with creating a limited amount of HTTP connections using Apache's HTTP client (3.x version).这里的答案都没有解决我的问题,这与使用 Apache 的 HTTP 客户端(3.x 版本)创建有限数量的 HTTP 连接有关。 Since it took me some hours to figure out a good setup, I'll share:由于我花了几个小时才找到一个好的设置,我将分享:

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

This creates a ThreadPoolExecutor which starts with five and holds a maximum of ten simultaneously running threads using CallerRunsPolicy for executing.这将创建一个ThreadPoolExecutor ,它以五个开头,并使用CallerRunsPolicy执行最多可容纳十个同时运行的线程。

Per the Javadoc for ThreadPoolExecutor:根据 ThreadPoolExecutor 的 Javadoc:

If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full .如果有超过 corePoolSize 但少于 maximumPoolSize 的线程正在运行,则只有在队列已满时才会创建新线程。 By setting corePoolSize and maximumPoolSize the same, you create a fixed-size thread pool.通过将 corePoolSize 和 maximumPoolSize 设置为相同,您可以创建一个固定大小的线程池。

(Emphasis mine.) (强调我的。)

jitter's answer is what you want, although mine answers your other question. jitter 的答案是你想要的,虽然我的回答了你的另一个问题。 :) :)

This is what you want (atleast I guess so).这就是你想要的(至少我猜是这样)。 For an explanation check Jonathan Feinberg answer有关解释,请查看Jonathan Feinberg 的回答

Executors.newFixedThreadPool(int n)

Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.创建一个线程池,该线程池重用固定数量的线程在共享的无界队列中运行。 At any point, at most nThreads threads will be active processing tasks.在任何时候,最多 nThreads 个线程将是活动的处理任务。 If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available.如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到有线程可用。 If any thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks.如果任何线程在关闭前的执行过程中因失败而终止,则在需要执行后续任务时,将有一个新线程取而代之。 The threads in the pool will exist until it is explicitly shutdown.池中的线程将一直存在,直到它被明确关闭。

there is one more option.还有一种选择。 Instead of using new SynchronousQueue you can use any other queue also, but you have to make sure its size is 1, so that will force executorservice to create new thread.除了使用 new SynchronousQueue,您还可以使用任何其他队列,但您必须确保其大小为 1,以便强制 executorservice 创建新线程。

Doesn't look as though any of the answers actually answer the question - in fact I can't see a way of doing this - even if you subclass from PooledExecutorService since many of the methods/properties are private eg making addIfUnderMaximumPoolSize was protected you could do the following:看起来好像没有任何答案实际上回答了这个问题 - 事实上我看不到这样做的方法 - 即使你从 PooledExecutorService 子类化,因为许多方法/属性都是私有的,例如让 addIfUnderMaximumPoolSize 受到保护,你可以执行以下操作:

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

The closest I got was this - but even that isn't a very good solution我得到的最接近的是这个 - 但即使这样也不是一个很好的解决方案

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);
         }
    };
 };

ps not tested the above ps没有测试以上

Here is another solution.这是另一种解决方案。 I think this solution behaves as you want it to (though not proud of this solution):我认为此解决方案的行为符合您的要求(尽管对此解决方案并不感到自豪):

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);
  1. You can use ThreadPoolExecutor as suggested by @sjlee您可以按照@sjlee 的建议使用ThreadPoolExecutor

    You can control the size of the pool dynamically.您可以动态控制池的大小。 Have a look at this question for more details :看看这个问题了解更多详情:

    Dynamic Thread Pool 动态线程池

    OR要么

  2. You can use newWorkStealingPool API, which has been introduced with java 8.您可以使用 java 8 中引入的newWorkStealingPool API。

     public static ExecutorService newWorkStealingPool()

    Creates a work-stealing thread pool using all available processors as its target parallelism level.使用所有可用的处理器作为其目标并行度级别来创建窃取工作的线程池。

By default, the parallelism level is set to number of CPU cores in your server.默认情况下,并行级别设置为服务器中的 CPU 内核数。 If you have 4 core CPU server, thread pool size would be 4. This API returns ForkJoinPool type of ExecutorService and allow work stealing of idle threads by stealing tasks from busy threads in ForkJoinPool.如果您有 4 核 CPU 服务器,线程池大小将为 4。此 API 返回ForkJoinPool类型的ExecutorService并允许通过从 ForkJoinPool 中的繁忙线程窃取任务来窃取空闲线程的工作。

The problem was summarized as follows:问题总结如下:

I want something exactly like the cached thread pool (it creates threads on demand and then kills them after some timeout) but with a limit on the number of threads that it can create and the ability to continue to queue additional tasks once it has hit its thread limit.我想要一些与缓存线程池完全相同的东西(它按需创建线程,然后在超时后杀死它们),但是它可以创建的线程数量有限制,并且一旦达到它就能够继续排队附加任务线程限制。

Before pointing to the solution I will explain why the following solutions don't work:在指向解决方案之前,我将解释为什么以下解决方案不起作用:

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

This will not queue any tasks when the limit of 3 is reached because SynchronousQueue, by definition, cannot hold any elements.当达到 3 的限制时,这不会对任何任务进行排队,因为 SynchronousQueue 根据定义不能容纳任何元素。

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

This will not create more than a single thread because ThreadPoolExecutor only creates threads exceeding the corePoolSize if the queue is full.这不会创建多个线程,因为 ThreadPoolExecutor 仅在队列已满时创建超过 corePoolSize 的线程。 But LinkedBlockingQueue is never full.但是 LinkedBlockingQueue 永远不会满。

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

This will not reuse threads until the corePoolSize has been reached because ThreadPoolExecutor increases the number of threads until the corePoolSize is reached even if existing threads are idle.在达到 corePoolSize 之前,这不会重用线程,因为即使现有线程空闲,ThreadPoolExecutor 也会增加线程数,直到达到 corePoolSize。 If you can live with this disadvantage then this is the easiest solution to the problem.如果你能忍受这个缺点,那么这是解决问题的最简单的方法。 It is also the solution described in "Java Concurrency in Practice" (footnote on p172).它也是“Java Concurrency in Practice”(p172 的脚注)中描述的解决方案。

The only complete solution to the described problem seems to be the one involving overriding the queue's offer method and writing a RejectedExecutionHandler as explained in the answers to this question: How to get the ThreadPoolExecutor to increase threads to max before queueing?所描述问题的唯一完整解决方案似乎是涉及覆盖队列的offer方法并编写RejectedExecutionHandler解决方案,如以下问题的答案中所述:如何在排队之前让 ThreadPoolExecutor 将线程增加到最大值?

This works for Java8+ (and other, for now..)这适用于 Java8+(以及其他,目前..)

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

where 3 is the limit of threads count, and 5 is timeout for idle threads.其中 3 是线程数的限制,5 是空闲线程的超时时间。

If you want to check if it works yourself , here is the code to do the job:如果你想自己检查它是否有效,这里是完成这项工作的代码:

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) {
    }
}

output of the code above for me is上面代码的输出对我来说是

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

I Recommend using Signal approach我推荐使用信号方法

from SignalExecutors class:来自SignalExecutors类:

ThreadPoolExecutor will only create a new thread if the provided queue returns false from offer().如果提供的队列从 offer() 返回 false,则 ThreadPoolExecutor 只会创建一个新线程。 That means if you give it an unbounded queue, it'll only ever create 1 thread, no matter how long the queue gets.这意味着如果你给它一个无界队列,它只会创建 1 个线程,不管队列有多长。 But if you bound the queue and submit more runnables than there are threads, your task is rejected and throws an exception.但是,如果您绑定队列并提交比线程数更多的可运行对象,则您的任务将被拒绝并引发异常。 So we make a queue that will always return false if it's non-empty to ensure new threads get created.因此,我们创建了一个队列,如果它不为空,它将始终返回 false 以确保创建新线程。 Then, if a task gets rejected, we simply add it to the queue.然后,如果任务被拒绝,我们只需将其添加到队列中。

    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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM