Impossible to make a cached thread pool with a size limit?

前端 未结 13 801
日久生厌
日久生厌 2020-11-28 00:31

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

相关标签:
13条回答
  • 2020-11-28 01:08

    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.

    tp = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<Runnable>());
    tp.allowCoreThreadTimeOut(true);
    
    0 讨论(0)
  • 2020-11-28 01:13

    The ThreadPoolExecutor has the following several key behaviors, and your problems can be explained by these behaviors.

    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).

    In the second example, the queue of choice is a LinkedBlockingQueue which has an unlimited size. Therefore, you get stuck with behavior #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. Therefore, the core size of 0 is an exception to the rule above. Thanks Steve for bringing that to my attention.

    0 讨论(0)
  • 2020-11-28 01:14

    This works for Java8+ (and other, for now..)

         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.

    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
    
    0 讨论(0)
  • 2020-11-28 01:17

    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). 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.

    0 讨论(0)
  • 2020-11-28 01:20

    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);
    
    0 讨论(0)
  • 2020-11-28 01:22

    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.

    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. Submit() will throw RejectedExecutionException if number of pending tasks exceeds Integer.MAX_VALUE. Not sure we will run out of memory first or this will happen.

    3. Has 4 core threads possible. Idle core threads automatically exit if idle for 5 seconds.So, yes, strictly on demand threads.Number can be varied using setThreads() method.

    4. Makes sure min number of core threads is never less than one, or else submit() will reject every task. 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.

    0 讨论(0)
提交回复
热议问题