当 corePoolSize = 0 时,ScheduledExecutorService 消耗 100% CPU
ScheduledExecutorService consumes 100% CPU when corePoolSize = 0
我 运行 在生产中遇到了一个有趣的问题。
我有以下 ScheduledThreadPool
分配代码:
ScheduledExecutorService executorService =
Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors() - 1);
线程池正在定期处理队列中的一些任务。在将服务部署到 单核 环境之前,一切都运行良好。显然,上面的行转换为:
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(0);
从那时起,JVM 进程 CPU 利用率一直保持在 100% 左右。当我将 Runtime.getRuntime().availableProcessors() - 1
更改为常量 1
时,问题就消失了。
找出根本原因花了一些时间,但我仍然不知道背后的原因。 ScheduledExecutorService
JavaDoc 指出:
/**
* 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);
}
基本上,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);
}
}
}
此代码在单核虚拟机上分配 ~30 MB 并且 JVM 进程消耗 ~100% CPU (在 Win 7/CentOS 7 上测试)。 JDK1.8.0.181.
通过更改字段:
final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(0);
至:
final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
CPU 消耗下降到正常 3-5%。
这是一个已知错误:JDK-8129861。它已在 JDK 9.
中修复
解决方法是将核心池大小至少设置为 1:
int corePoolSize = Math.max(Runtime.getRuntime().availableProcessors() - 1, 1);
我 运行 在生产中遇到了一个有趣的问题。
我有以下 ScheduledThreadPool
分配代码:
ScheduledExecutorService executorService =
Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors() - 1);
线程池正在定期处理队列中的一些任务。在将服务部署到 单核 环境之前,一切都运行良好。显然,上面的行转换为:
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(0);
从那时起,JVM 进程 CPU 利用率一直保持在 100% 左右。当我将 Runtime.getRuntime().availableProcessors() - 1
更改为常量 1
时,问题就消失了。
找出根本原因花了一些时间,但我仍然不知道背后的原因。 ScheduledExecutorService
JavaDoc 指出:
/**
* 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);
}
基本上,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);
}
}
}
此代码在单核虚拟机上分配 ~30 MB 并且 JVM 进程消耗 ~100% CPU (在 Win 7/CentOS 7 上测试)。 JDK1.8.0.181.
通过更改字段:
final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(0);
至:
final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
CPU 消耗下降到正常 3-5%。
这是一个已知错误:JDK-8129861。它已在 JDK 9.
中修复解决方法是将核心池大小至少设置为 1:
int corePoolSize = Math.max(Runtime.getRuntime().availableProcessors() - 1, 1);