
com.github.javaclub.toolbox.spring.MdcThreadPoolTaskExecutor Maven / Gradle / Ivy
/*
* @(#)MdcThreadPoolTaskExecutor.java 2022-3-10
*
* Copyright (c) 2022. All Rights Reserved.
*
*/
package com.github.javaclub.toolbox.spring;
import java.text.MessageFormat;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureTask;
import com.github.javaclub.toolbox.ToolBox.Maths;
import com.github.javaclub.toolbox.ToolBox.Numbers;
import com.github.javaclub.toolbox.ToolBox.Objects;
import com.github.javaclub.toolbox.ToolBox.Systems;
import com.github.javaclub.toolbox.thread.ExecutorServiceInstance;
import com.github.javaclub.toolbox.thread.MdcThreadPoolExecutor;
import com.github.javaclub.toolbox.thread.memlimit.MemorySafeLinkedBlockingQueue;
/**
* MdcThreadPoolTaskExecutor
*
* @version $Id: MdcThreadPoolTaskExecutor.java 2022-3-10 11:49:05 Exp $
*/
public class MdcThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
private static final long serialVersionUID = 22934404848245975L;
private static final Logger log = LoggerFactory.getLogger(MdcThreadPoolTaskExecutor.class);
private final Object poolSizeMonitor = new Object();
private int corePoolSize = 1;
private int maxPoolSize = Integer.MAX_VALUE;
private int keepAliveSeconds = 60;
private int queueCapacity = Integer.MAX_VALUE;
private boolean allowCoreThreadTimeOut = false;
private boolean allowCorePoolSizeDynamicChanged = false;
@Nullable
private TaskDecorator taskDecorator;
@Nullable
private ThreadPoolExecutor threadPoolExecutor;
private String threadPoolTaskExecutorName = "defaultThreadPoolTaskExecutor";
// Runnable decorator to user-level FutureTask, if different
private final Map decoratedTaskMap =
new ConcurrentReferenceHashMap(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);
public MdcThreadPoolTaskExecutor() {
ExecutorServiceInstance.get().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (null == threadPoolExecutor) {
return;
}
monitorThread();
}
}, 6L, 180L, TimeUnit.SECONDS);
}
public MdcThreadPoolTaskExecutor(String name, int queueCapacity) {
this.threadPoolTaskExecutorName = name;
this.queueCapacity = queueCapacity;
setThreadNamePrefix(name);
ExecutorServiceInstance.get().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (null == threadPoolExecutor) {
return;
}
monitorThread();
}
}, 6L, 180L, TimeUnit.SECONDS);
}
protected void monitorThread() {
BlockingQueue> queue = getThreadPoolExecutor().getQueue();
int queueTotalNum = queue.size() + queue.remainingCapacity();
double percent = Maths.div(queue.size(), queueTotalNum, 10);
String format = MessageFormat.format(threadPoolTaskExecutorName +
": [" +
"corePoolSize:{0}, " +
"maxPoolSize:{1}, " +
"activeCount:{2}, " +
"completedTaskCount:{3}, " +
"queueCapacity:{4}, " +
"queueWaitingNum:{5}, " +
"queueRemainingCapacity:{6}, " +
"queueUsingRate:{7}" + "]",
getThreadPoolExecutor().getCorePoolSize(),
getThreadPoolExecutor().getMaximumPoolSize(),
getThreadPoolExecutor().getActiveCount(),
getThreadPoolExecutor().getCompletedTaskCount(),
Numbers.formatDouble(queueTotalNum, 0),
Numbers.formatDouble(queue.size(), 0),
Numbers.formatDouble(queue.remainingCapacity(), 0),
Numbers.formatPercent(percent, 2, 2)
);
if (log.isInfoEnabled() && percent > 0.5D) {
log.info(format);
}
if (allowCorePoolSizeDynamicChanged) {
int oldCorePoolSize = getCorePoolSize();
int oldMaxPoolSize = getMaxPoolSize();
if (percent > 0.8D) {
int cpuFactorNum = 2*Systems.cupNum() + 1;
if (this.corePoolSize < cpuFactorNum) {
setCorePoolSize(queueTotalNum);
setMaxPoolSize(queueTotalNum);
log.info("线程池(" + threadPoolTaskExecutorName + ")动态扩容: corePoolSize({} => {}), maxPoolSize({} => {})",
oldCorePoolSize, getCorePoolSize(), oldMaxPoolSize, getMaxPoolSize());
}
} else if (percent < 0.25D) {
if (!Objects.equals(oldCorePoolSize, Systems.cupNum())) {
setCorePoolSize(Systems.cupNum());
setMaxPoolSize(Systems.cupNum() + 1);
log.info("线程池(" + threadPoolTaskExecutorName + ")动态调整: corePoolSize({} => {}), maxPoolSize({} => {})",
oldCorePoolSize, getCorePoolSize(), oldMaxPoolSize, getMaxPoolSize());
}
}
}
}
/**
* Set the ThreadPoolExecutor's core pool size.
* Default is 1.
* This setting can be modified at runtime, for example through JMX.
*/
public void setCorePoolSize(int corePoolSize) {
synchronized (this.poolSizeMonitor) {
this.corePoolSize = corePoolSize;
if (this.threadPoolExecutor != null) {
this.threadPoolExecutor.setCorePoolSize(corePoolSize);
}
}
}
/**
* Return the ThreadPoolExecutor's core pool size.
*/
public int getCorePoolSize() {
synchronized (this.poolSizeMonitor) {
return this.corePoolSize;
}
}
/**
* Set the ThreadPoolExecutor's maximum pool size.
* Default is {@code Integer.MAX_VALUE}.
*
This setting can be modified at runtime, for example through JMX.
*/
public void setMaxPoolSize(int maxPoolSize) {
synchronized (this.poolSizeMonitor) {
this.maxPoolSize = maxPoolSize;
if (this.threadPoolExecutor != null) {
this.threadPoolExecutor.setMaximumPoolSize(maxPoolSize);
}
}
}
/**
* Return the ThreadPoolExecutor's maximum pool size.
*/
public int getMaxPoolSize() {
synchronized (this.poolSizeMonitor) {
return this.maxPoolSize;
}
}
/**
* Set the ThreadPoolExecutor's keep-alive seconds.
* Default is 60.
*
This setting can be modified at runtime, for example through JMX.
*/
public void setKeepAliveSeconds(int keepAliveSeconds) {
synchronized (this.poolSizeMonitor) {
this.keepAliveSeconds = keepAliveSeconds;
if (this.threadPoolExecutor != null) {
this.threadPoolExecutor.setKeepAliveTime(keepAliveSeconds, TimeUnit.SECONDS);
}
}
}
/**
* Return the ThreadPoolExecutor's keep-alive seconds.
*/
public int getKeepAliveSeconds() {
synchronized (this.poolSizeMonitor) {
return this.keepAliveSeconds;
}
}
/**
* Set the capacity for the ThreadPoolExecutor's BlockingQueue.
* Default is {@code Integer.MAX_VALUE}.
*
Any positive value will lead to a LinkedBlockingQueue instance;
* any other value will lead to a SynchronousQueue instance.
* @see java.util.concurrent.LinkedBlockingQueue
* @see java.util.concurrent.SynchronousQueue
*/
public void setQueueCapacity(int queueCapacity) {
this.queueCapacity = queueCapacity;
}
/**
* Specify whether to allow core threads to time out. This enables dynamic
* growing and shrinking even in combination with a non-zero queue (since
* the max pool size will only grow once the queue is full).
*
Default is "false".
* @see java.util.concurrent.ThreadPoolExecutor#allowCoreThreadTimeOut(boolean)
*/
public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
}
public void setAllowCorePoolSizeDynamicChanged(boolean allowCorePoolSizeDynamicChanged) {
this.allowCorePoolSizeDynamicChanged = allowCorePoolSizeDynamicChanged;
}
/**
* Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
* about to be executed.
*
Note that such a decorator is not necessarily being applied to the
* user-supplied {@code Runnable}/{@code Callable} but rather to the actual
* execution callback (which may be a wrapper around the user-supplied task).
*
The primary use case is to set some execution context around the task's
* invocation, or to provide some monitoring/statistics for task execution.
* @since 4.3
*/
public void setTaskDecorator(TaskDecorator taskDecorator) {
this.taskDecorator = taskDecorator;
}
/**
* Note: This method exposes an {@link ExecutorService} to its base class
* but stores the actual {@link ThreadPoolExecutor} handle internally.
* Do not override this method for replacing the executor, rather just for
* decorating its {@code ExecutorService} handle or storing custom state.
*/
@Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
BlockingQueue queue = createQueue(this.queueCapacity);
ThreadPoolExecutor executor;
if (this.taskDecorator != null) {
executor = new MdcThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler) {
@Override
public void execute(Runnable command) {
Runnable decorated = taskDecorator.decorate(command);
if (decorated != command) {
decoratedTaskMap.put(decorated, command);
}
super.execute(decorated);
}
};
}
else {
executor = new MdcThreadPoolExecutor(
this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
queue, threadFactory, rejectedExecutionHandler);
}
if (this.allowCoreThreadTimeOut) {
executor.allowCoreThreadTimeOut(true);
}
this.threadPoolExecutor = executor;
return executor;
}
/**
* Create the BlockingQueue to use for the ThreadPoolExecutor.
* A LinkedBlockingQueue instance will be created for a positive
* capacity value; a SynchronousQueue else.
* @param queueCapacity the specified queue capacity
* @return the BlockingQueue instance
* @see java.util.concurrent.LinkedBlockingQueue
* @see java.util.concurrent.SynchronousQueue
* @see com.github.javaclub.toolbox.thread.memlimit.MemorySafeLinkedBlockingQueue
*/
protected BlockingQueue createQueue(int queueCapacity) {
if (queueCapacity > 0) {
return new MemorySafeLinkedBlockingQueue(queueCapacity);
} else {
return new SynchronousQueue();
}
}
/**
* Return the underlying ThreadPoolExecutor for native access.
* @return the underlying ThreadPoolExecutor (never {@code null})
* @throws IllegalStateException if the ThreadPoolTaskExecutor hasn't been initialized yet
*/
public ThreadPoolExecutor getThreadPoolExecutor() throws IllegalStateException {
Assert.state(this.threadPoolExecutor != null, "ThreadPoolTaskExecutor not initialized");
return this.threadPoolExecutor;
}
/**
* Return the current pool size.
* @see java.util.concurrent.ThreadPoolExecutor#getPoolSize()
*/
public int getPoolSize() {
if (this.threadPoolExecutor == null) {
// Not initialized yet: assume core pool size.
return this.corePoolSize;
}
return this.threadPoolExecutor.getPoolSize();
}
/**
* Return the number of currently active threads.
* @see java.util.concurrent.ThreadPoolExecutor#getActiveCount()
*/
public int getActiveCount() {
if (this.threadPoolExecutor == null) {
// Not initialized yet: assume no active threads.
return 0;
}
return this.threadPoolExecutor.getActiveCount();
}
@Override
public void execute(Runnable task) {
Executor executor = getThreadPoolExecutor();
try {
executor.execute(task);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}
@Override
public Future> submit(Runnable task) {
ExecutorService executor = getThreadPoolExecutor();
try {
return executor.submit(task);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
@Override
public Future submit(Callable task) {
ExecutorService executor = getThreadPoolExecutor();
try {
return executor.submit(task);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
@Override
public ListenableFuture> submitListenable(Runnable task) {
ExecutorService executor = getThreadPoolExecutor();
try {
ListenableFutureTask