当corePoolSize = 0时,ScheduledExecutorService消耗100%的CPU

Mik*_*kov 3 java performance threadpool threadpoolexecutor

我在制作中遇到了一个有趣的问题.

我有以下ScheduledThreadPool分配代码:

ScheduledExecutorService executorService =
            Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors() - 1);
Run Code Online (Sandbox Code Playgroud)

线程池定期处理队列中的一些任务.一切都运行正常,直到服务部署在单个核心环境中.显然,上面的行转换为:

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(0);
Run Code Online (Sandbox Code Playgroud)

从那时起,JVM进程的CPU利用率一直在100%左右.在我改变Runtime.getRuntime().availableProcessors() - 1为不变1的那一刻,问题已经消失.

找出根本原因花了一些时间,但我仍然不知道背后的原因.ScheduledExecutorServiceJavaDoc声明:

/**
 * Creates a thread pool that can schedule commands to run after a
 * given delay, or to execute periodically.
 * @param corePoolSize the number of threads to keep in the pool,
 * even if they are idle
 * @return a newly created scheduled thread pool
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 */
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
Run Code Online (Sandbox Code Playgroud)

基本上,0(零)是线程池实例化的有效参数,但它对此值的工作非常奇怪.

有人可以解释为什么这样吗?

简单且可验证的测试用例

import java.util.Queue;
import java.util.concurrent.*;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        MessageTaskExecutor asyncEmailGatewayTaskExecutor = new MessageTaskExecutor();

        // Infinitely add new tasks to the queue every second
        for (int i = 1; ; i++) {
            System.out.println(String.format("Adding message #%s to the queue", i));

            asyncEmailGatewayTaskExecutor.putMessageIntoQueue(i);

            Thread.sleep(1_000);
        }
    }

    static class MessageTaskExecutor {

        static final int INITIAL_DELAY_SECONDS = 1;
        static final int PROCESSING_RATE_MILLISECONDS = 5_000;

        final Queue<Runnable> messageQueue = new ArrayBlockingQueue<>(1_000_000);
        final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(0);

        MessageTaskExecutor() {
            // Scavenging Message Tasks Queue every 'PROCESSING_RATE_MILLISECONDS'. Initial delay is fixed for 'INITIAL_DELAY_SECONDS'
            executorService.schedule(this::processEmailTasks, INITIAL_DELAY_SECONDS, TimeUnit.SECONDS);
        }

        void putMessageIntoQueue(int messageId) {
            Runnable messageTask = () -> System.out.println(String.format("Message #%s is getting processed!", messageId));

            messageQueue.offer(messageTask);
        }

        void processEmailTasks() {
            System.out.println(String.format("There are %s messages in the queue. Processing the messages...", messageQueue.size()));

            // Processing messages queue
            while (!messageQueue.isEmpty()) {
                executorService.submit(messageQueue.poll()); // Submitting task to executor service
            }

            // Re-scheduling processing job
            executorService.schedule(this::processEmailTasks, PROCESSING_RATE_MILLISECONDS, TimeUnit.MILLISECONDS);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码分配~30 MB,JVM进程在单核虚拟机上消耗~ 100%CPU(在Win 7/CentOS 7上测试).JDK 1.8.0.181.

通过改变字段:

final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(0);
Run Code Online (Sandbox Code Playgroud)

至:

final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
Run Code Online (Sandbox Code Playgroud)

CPU消耗降至正常3-5%.

apa*_*gin 5

这是一个已知的错误:JDK-8129861.它已在JDK 9中修复.

解决方法是将核心池大小设置为至少1:

int corePoolSize = Math.max(Runtime.getRuntime().availableProcessors() - 1, 1);
Run Code Online (Sandbox Code Playgroud)