package org.springside.modules.utils.concurrent.threadpool;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* From Tomcat 8.5.6, 传统的FixedThreadPool有Queue但线程数量不变,而CachedThreadPool线程数可变但没有Queue
*
* Tomcat的线程池,通过控制TaskQueue,线程数,但线程数到达最大时会进入Queue中.
*
* 代码从Tomcat复制,主要修改包括:
*
* 1. 删除定期重启线程避免内存泄漏的功能,
*
* 2. TaskQueue中可能3次有锁的读取线程数量,改为只读取1次,这把锁也是这个实现里的唯一遗憾了。
*
* @author calvin
*
*/
public class QueuableCachedThreadPool extends java.util.concurrent.ThreadPoolExecutor {
/**
* The number of tasks submitted but not yet finished. This includes tasks in the queue and tasks that have been
* handed to a worker thread but the latter did not start executing the task yet. This number is always greater or
* equal to {@link #getActiveCount()}.
*/
private final AtomicInteger submittedCount = new AtomicInteger(0);
public QueuableCachedThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
ControllableQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
workQueue.setParent(this);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
submittedCount.decrementAndGet();
}
public int getSubmittedCount() {
return submittedCount.get();
}
/**
* {@inheritDoc}
*/
@Override
public void execute(Runnable command) {
execute(command, 0, TimeUnit.MILLISECONDS);
}
/**
* Executes the given command at some time in the future. The command may execute in a new thread, in a pooled
* thread, or in the calling thread, at the discretion of the <tt>Executor</tt> implementation. If no threads are
* available, it will be added to the work queue. If the work queue is full, the system will wait for the specified
* time and it throw a RejectedExecutionException if the queue is still full after that.
*
* @param command the runnable task
* @param timeout A timeout for the completion of the task
* @param unit The timeout time unit
* @throws RejectedExecutionException if this task cannot be accepted for execution - the queue is full
* @throws NullPointerException if command or unit is null
*/
public void execute(Runnable command, long timeout, TimeUnit unit) {
submittedCount.incrementAndGet();
try {
super.execute(command);
} catch (RejectedExecutionException rx) {
final ControllableQueue queue = (ControllableQueue) super.getQueue();
try {
if (!queue.force(command, timeout, unit)) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException("Queue capacity is full.");
}
} catch (InterruptedException x) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException(x);
}
}
}
public static class ControllableQueue extends LinkedBlockingQueue<Runnable> {
private static final long serialVersionUID = 5044057462066661171L;
private volatile QueuableCachedThreadPool parent = null;
public ControllableQueue(int capacity) {
super(capacity);
}
public void setParent(QueuableCachedThreadPool tp) {
parent = tp;
}
public boolean force(Runnable o) {
if (parent.isShutdown()) {
throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
}
return super.offer(o); // forces the item onto the queue, to be used if the task is rejected
}
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if (parent.isShutdown()) {
throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
}
return super.offer(o, timeout, unit); // forces the item onto the queue, to be used if the task is rejected
}
@Override
public boolean offer(Runnable o) {
// springside: threadPool.getPoolSize() 是个有锁的操作,所以尽量减少
int currentPoolSize = parent.getPoolSize();
// we are maxed out on threads, simply queue the object
if (currentPoolSize >= parent.getMaximumPoolSize()) {
return super.offer(o);
}
// we have idle threads, just add it to the queue
if (parent.getSubmittedCount() < currentPoolSize) {
return super.offer(o);
}
// if we have less threads than maximum force creation of a new thread
if (currentPoolSize < parent.getMaximumPoolSize()) {
return false;
}
// if we reached here, we need to add it to the queue
return super.offer(o);
}
}
}