All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.javaclub.toolbox.spring.MdcThreadPoolTaskExecutor Maven / Gradle / Ivy

The newest version!
/*
 * @(#)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 future = new ListenableFutureTask(task, null); executor.execute(future); return future; } catch (RejectedExecutionException ex) { throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex); } } @Override public ListenableFuture submitListenable(Callable task) { ExecutorService executor = getThreadPoolExecutor(); try { ListenableFutureTask future = new ListenableFutureTask(task); executor.execute(future); return future; } catch (RejectedExecutionException ex) { throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex); } } protected void cancelRemainingTask(Runnable task) { if (task instanceof Future) { ((Future) task).cancel(true); } // Cancel associated user-level Future handle as well Object original = this.decoratedTaskMap.get(task); if (original instanceof Future) { ((Future) original).cancel(true); } } }