Search code examples
javamultithreadingperformance

ThreadPoolExecutor executing in single thread even though corePoolSize is set more


My problem is related to the executor returned by Executors.newFixedThreadPool(1) which is explained after the following program:

public static void main(String[] args) throws InterruptedException {
    AtomicInteger atomicInteger = new AtomicInteger(0);
    ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
    List<Runnable> runnableList = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        runnableList.add(task(atomicInteger.incrementAndGet(), threadPoolExecutor));
    }
    System.out.println("Starting Stage-1.");
    threadPoolExecutor.setCorePoolSize(4);
    runnableList.forEach(threadPoolExecutor::execute);

    waitForCompletion(threadPoolExecutor);
    System.out.println("Stage-1 complete.");

    threadPoolExecutor.setCorePoolSize(0);
    TimeUnit.SECONDS.sleep(10);

    System.out.println("Starting Stage-2.");
    threadPoolExecutor.setCorePoolSize(6);
    runnableList.forEach(threadPoolExecutor::execute);

    waitForCompletion(threadPoolExecutor);
    System.out.println("Stage-2 complete.");
    threadPoolExecutor.shutdown();
}

static Runnable task(int count, ThreadPoolExecutor threadPoolExecutor) {
    return () -> {
        LocalTime start;
        LocalTime end;
        try {
            start = LocalTime.now();
            TimeUnit.SECONDS.sleep(5);
            end = LocalTime.now();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        String log = "Thread: " + count +
                ",  Start: " + start +
                ",  Stop: " + end +
                ",  ActiveCount: " + threadPoolExecutor.getActiveCount() +
                ",  CorePoolSize: " + threadPoolExecutor.getCorePoolSize();
        System.out.println(log);
    };
}

static void waitForCompletion(ThreadPoolExecutor threadPoolExecutor) throws InterruptedException {
    while (threadPoolExecutor.getActiveCount() != 0) {
        TimeUnit.SECONDS.sleep(5);
    }
}

Output:

Starting Stage-1.
Thread: 2,  Start: 18:27:07.482,  Stop: 18:27:12.487,  ActiveCount: 4,  CorePoolSize: 4
Thread: 3,  Start: 18:27:07.482,  Stop: 18:27:12.487,  ActiveCount: 4,  CorePoolSize: 4
Thread: 1,  Start: 18:27:07.482,  Stop: 18:27:12.487,  ActiveCount: 4,  CorePoolSize: 4
Thread: 4,  Start: 18:27:07.482,  Stop: 18:27:12.487,  ActiveCount: 4,  CorePoolSize: 4
Thread: 5,  Start: 18:27:12.488,  Stop: 18:27:17.491,  ActiveCount: 1,  CorePoolSize: 4
Thread: 6,  Start: 18:27:17.492,  Stop: 18:27:22.497,  ActiveCount: 1,  CorePoolSize: 4
Thread: 7,  Start: 18:27:22.497,  Stop: 18:27:27.502,  ActiveCount: 1,  CorePoolSize: 4
Thread: 8,  Start: 18:27:27.502,  Stop: 18:27:32.506,  ActiveCount: 1,  CorePoolSize: 4
Thread: 9,  Start: 18:27:32.506,  Stop: 18:27:37.512,  ActiveCount: 1,  CorePoolSize: 4
Thread: 10,  Start: 18:27:37.512,  Stop: 18:27:42.515,  ActiveCount: 1,  CorePoolSize: 4
Stage-1 complete.
Starting Stage-2.
Thread: 3,  Start: 18:27:57.504,  Stop: 18:28:02.506,  ActiveCount: 6,  CorePoolSize: 6
Thread: 5,  Start: 18:27:57.504,  Stop: 18:28:02.506,  ActiveCount: 6,  CorePoolSize: 6
Thread: 6,  Start: 18:27:57.504,  Stop: 18:28:02.506,  ActiveCount: 5,  CorePoolSize: 6
Thread: 4,  Start: 18:27:57.504,  Stop: 18:28:02.506,  ActiveCount: 3,  CorePoolSize: 6
Thread: 2,  Start: 18:27:57.504,  Stop: 18:28:02.506,  ActiveCount: 3,  CorePoolSize: 6
Thread: 1,  Start: 18:27:57.504,  Stop: 18:28:02.506,  ActiveCount: 1,  CorePoolSize: 6
Thread: 7,  Start: 18:28:02.507,  Stop: 18:28:07.512,  ActiveCount: 1,  CorePoolSize: 6
Thread: 8,  Start: 18:28:07.512,  Stop: 18:28:12.517,  ActiveCount: 1,  CorePoolSize: 6
Thread: 9,  Start: 18:28:12.517,  Stop: 18:28:17.522,  ActiveCount: 1,  CorePoolSize: 6
Thread: 10,  Start: 18:28:17.522,  Stop: 18:28:22.524,  ActiveCount: 1,  CorePoolSize: 6
Stage-2 complete.

In the above program, you will see the 10 Threads being executed, first with a corePoolSize of 4. After the execution of the first four threads, the execution continues executing sequentially even though the corePoolSize is set higher than 1. I have demonstrated the above findings twice with 2 stages with corePoolSize of 4 and 6, confirming the uniformity of the behaviour.

My expectation is when there is empty slot in the executor, it should utilise that.


Solution

  • I am answering my question. Thanks to @tgdavies for providing the exception thrown in JDK 20. The solution is to increase the maxPoolSize.

    After setting the maxPoolSize to something like

    threadPoolExecutor.setMaximumPoolSize(Runtime.getRuntime().availableProcessors());
    

    Things are executing parallely.