/**
* Alipay.com Inc.
* Copyright (c) 2004-2012 All Rights Reserved.
*/
package com.alipay.zdal.datasource.resource.util.threadpool;
import java.util.Collections;
import java.util.Map;
import org.apache.log4j.Logger;
import com.alipay.zdal.datasource.resource.util.collection.WeakValueHashMap;
import com.alipay.zdal.datasource.resource.util.concurrent.BoundedLinkedQueue;
import com.alipay.zdal.datasource.resource.util.concurrent.Heap;
import com.alipay.zdal.datasource.resource.util.concurrent.SynchronizedBoolean;
import com.alipay.zdal.datasource.resource.util.concurrent.SynchronizedInt;
import com.alipay.zdal.datasource.resource.util.concurrent.ThreadFactory;
/**
* A basic thread pool.
*
* @author ����
* @version $Id: BasicThreadPool.java, v 0.1 2014-1-6 ����05:43:37 Exp $
*/
public class BasicThreadPool implements ThreadPool, BasicThreadPoolMBean {
// Constants -----------------------------------------------------
/** The jboss thread group */
private static final ThreadGroup JBOSS_THREAD_GROUP = new ThreadGroup(
"JBoss Pooled Threads");
/** The thread groups */
private static final Map threadGroups = Collections
.synchronizedMap(new WeakValueHashMap());
/** The internal pool number */
private static final SynchronizedInt lastPoolNumber = new SynchronizedInt(0);
private static Logger log = Logger
.getLogger(BasicThreadPool.class);
// Attributes ----------------------------------------------------
/** The thread pool name */
private String name;
/** The internal pool number */
private final int poolNumber;
/** The blocking mode */
private BlockingMode blockingMode = BlockingMode.ABORT;
/** The pooled executor */
private final MinPooledExecutor executor;
/** The queue */
private final BoundedLinkedQueue queue;
/** The thread group */
private ThreadGroup threadGroup;
/** The last thread number */
private final SynchronizedInt lastThreadNumber = new SynchronizedInt(0);
/** Has the pool been stopped? */
private final SynchronizedBoolean stopped = new SynchronizedBoolean(false);
/** The Heap<TimeoutInfo> of tasks ordered by their completion timeout */
private final Heap tasksWithTimeouts = new Heap(13);
/** The task completion timeout monitor runnable */
private TimeoutMonitor timeoutTask;
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
/**
* Constructs a new thread pool
*/
public BasicThreadPool() {
this("ThreadPool");
}
/**
* Constructs a new thread pool with a default queue size of 1024, max pool
* size of 100, min pool size of 100, and a keep alive of 60 seconds.
*
* @param name the pool name
*/
public BasicThreadPool(String name) {
this(name, JBOSS_THREAD_GROUP);
}
/**
* Constructs a new thread pool with a default queue size of 1024, max pool
* size of 100, min pool size of 100, and a keep alive of 60 seconds.
*
* @param name the pool name
* @param threadGroup threadGroup
*/
public BasicThreadPool(String name, ThreadGroup threadGroup) {
ThreadFactory factory = new ThreadPoolThreadFactory();
queue = new BoundedLinkedQueue(1024);
executor = new MinPooledExecutor(queue, 100);
executor.setMinimumPoolSize(100);
executor.setKeepAliveTime(60 * 1000);
executor.setThreadFactory(factory);
executor.abortWhenBlocked();
poolNumber = lastPoolNumber.increment();
setName(name);
this.threadGroup = threadGroup;
}
// Public --------------------------------------------------------
// ThreadPool ----------------------------------------------------
public void stop(boolean immediate) {
if (log.isDebugEnabled()) {
log.debug("stop, immediate=" + immediate);
}
stopped.set(true);
if (immediate) {
executor.shutdownNow();
} else {
executor.shutdownAfterProcessingCurrentlyQueuedTasks();
}
}
public void waitForTasks() throws InterruptedException {
executor.awaitTerminationAfterShutdown();
}
public void waitForTasks(long maxWaitTime) throws InterruptedException {
executor.awaitTerminationAfterShutdown(maxWaitTime);
}
public void runTaskWrapper(TaskWrapper wrapper) {
if (log.isDebugEnabled()) {
log.debug("runTaskWrapper, wrapper=" + wrapper);
}
if (stopped.get()) {
wrapper.rejectTask(new ThreadPoolStoppedException("Thread pool has been stopped"));
return;
}
wrapper.acceptTask();
long completionTimeout = wrapper.getTaskCompletionTimeout();
TimeoutInfo info = null;
if (completionTimeout > 0) {
checkTimeoutMonitor();
// Install the task in the
info = new TimeoutInfo(wrapper, completionTimeout);
tasksWithTimeouts.insert(info);
}
int waitType = wrapper.getTaskWaitType();
switch (waitType) {
case Task.WAIT_FOR_COMPLETE: {
executeOnThread(wrapper);
break;
}
default: {
execute(wrapper);
}
}
waitForTask(wrapper);
}
public void runTask(Task task) {
BasicTaskWrapper wrapper = new BasicTaskWrapper(task);
runTaskWrapper(wrapper);
}
public void run(Runnable runnable) {
run(runnable, 0, 0);
}
public void run(Runnable runnable, long startTimeout, long completeTimeout) {
RunnableTaskWrapper wrapper = new RunnableTaskWrapper(runnable, startTimeout,
completeTimeout);
runTaskWrapper(wrapper);
}
public ThreadGroup getThreadGroup() {
return threadGroup;
}
// ThreadPoolMBean implementation --------------------------------
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPoolNumber() {
return poolNumber;
}
public String getThreadGroupName() {
return threadGroup.getName();
}
public void setThreadGroupName(String threadGroupName) {
ThreadGroup group;
synchronized (threadGroups) {
group = (ThreadGroup) threadGroups.get(threadGroupName);
if (group == null) {
group = new ThreadGroup(JBOSS_THREAD_GROUP, threadGroupName);
threadGroups.put(threadGroupName, group);
}
}
threadGroup = group;
}
public int getQueueSize() {
return queue.size();
}
public int getMaximumQueueSize() {
return queue.capacity();
}
public void setMaximumQueueSize(int size) {
queue.setCapacity(size);
}
public int getPoolSize() {
return executor.getPoolSize();
}
public int getMinimumPoolSize() {
return executor.getMinimumPoolSize();
}
public void setMinimumPoolSize(int size) {
synchronized (executor) {
executor.setKeepAliveSize(size);
// Don't let the min size > max size
if (executor.getMaximumPoolSize() < size) {
executor.setMinimumPoolSize(size);
executor.setMaximumPoolSize(size);
}
}
}
public int getMaximumPoolSize() {
return executor.getMaximumPoolSize();
}
public void setMaximumPoolSize(int size) {
synchronized (executor) {
executor.setMinimumPoolSize(size);
executor.setMaximumPoolSize(size);
// Don't let the min size > max size
if (executor.getKeepAliveSize() > size)
executor.setKeepAliveSize(size);
}
}
public long getKeepAliveTime() {
return executor.getKeepAliveTime();
}
public void setKeepAliveTime(long time) {
executor.setKeepAliveTime(time);
}
public BlockingMode getBlockingMode() {
return blockingMode;
}
public void setBlockingMode(BlockingMode mode) {
blockingMode = mode;
if (blockingMode == BlockingMode.RUN) {
executor.runWhenBlocked();
} else if (blockingMode == BlockingMode.WAIT) {
executor.waitWhenBlocked();
} else if (blockingMode == BlockingMode.DISCARD) {
executor.discardWhenBlocked();
} else if (blockingMode == BlockingMode.DISCARD_OLDEST) {
executor.discardOldestWhenBlocked();
} else if (blockingMode == BlockingMode.ABORT) {
executor.abortWhenBlocked();
} else {
throw new IllegalArgumentException("Failed to recognize mode: " + mode);
}
}
/**
* For backward compatibility with the previous string based mode
* @param name - the string form of the mode enum
*/
public void setBlockingMode(String name) {
blockingMode = BlockingMode.toBlockingMode(name);
if (blockingMode == null)
blockingMode = BlockingMode.ABORT;
}
public ThreadPool getInstance() {
return this;
}
public void stop() {
stop(false);
}
// Object overrides ----------------------------------------------
@Override
public String toString() {
return name + '(' + poolNumber + ')';
}
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
/**
* Execute a task on the same thread
*
* @param wrapper the task wrapper
*/
protected void executeOnThread(TaskWrapper wrapper) {
if (log.isDebugEnabled()) {
log.debug("executeOnThread, wrapper=" + wrapper);
}
wrapper.run();
}
/**
* Execute a task
*
* @param wrapper the task wrapper
*/
protected void execute(TaskWrapper wrapper) {
if (log.isDebugEnabled()) {
log.debug("execute, wrapper=" + wrapper);
}
try {
executor.execute(wrapper);
} catch (Throwable t) {
wrapper.rejectTask(new ThreadPoolFullException(t.toString()));
}
}
/**
* Wait for a task
*
* @param wrapper the task wrapper
*/
protected void waitForTask(TaskWrapper wrapper) {
wrapper.waitForTask();
}
/**
* Used to lazily create the task completion timeout thread and monitor
*/
protected synchronized void checkTimeoutMonitor() {
if (timeoutTask == null) {
timeoutTask = new TimeoutMonitor(name, log);
}
}
protected TimeoutInfo getNextTimeout() {
TimeoutInfo info = (TimeoutInfo) this.tasksWithTimeouts.extract();
return info;
}
// Private -------------------------------------------------------
// Inner classes -------------------------------------------------
/**
* A factory for threads
*/
private class ThreadPoolThreadFactory implements ThreadFactory {
public Thread newThread(Runnable runnable) {
String threadName = BasicThreadPool.this.toString() + "-"
+ lastThreadNumber.increment();
Thread thread = new Thread(threadGroup, runnable, threadName);
thread.setDaemon(true);
return thread;
}
}
/** An encapsulation of a task and its completion timeout
*/
private static class TimeoutInfo implements Comparable {
long start;
long timeoutMS;
TaskWrapper wrapper;
boolean firstStop;
TimeoutInfo(TaskWrapper wrapper, long timeout) {
this.start = System.currentTimeMillis();
this.timeoutMS = start + timeout;
this.wrapper = wrapper;
}
public void setTimeout(long timeout) {
this.start = System.currentTimeMillis();
this.timeoutMS = start + timeout;
}
/** Order TimeoutInfo based on the timestamp at which the task needs to
* be completed by.
* @param o a TimeoutInfo
* @return the diff between this timeoutMS and the argument timeoutMS
*/
public int compareTo(Object o) {
TimeoutInfo ti = (TimeoutInfo) o;
long to0 = timeoutMS;
long to1 = ti.timeoutMS;
int diff = (int) (to0 - to1);
return diff;
}
TaskWrapper getTaskWrapper() {
return wrapper;
}
public long getTaskCompletionTimeout() {
return wrapper.getTaskCompletionTimeout();
}
/** Get the time remaining to the complete timeout timestamp in MS.
* @param now - the current System.currentTimeMillis value
* @return the time remaining to the complete timeout timestamp in MS.
*/
public long getTaskCompletionTimeout(long now) {
return timeoutMS - now;
}
/** Invoke stopTask on the wrapper and indicate whether this was the first
* time the task has been notified to stop.
* @return true if this is the first stopTask, false on the second.
*/
public boolean stopTask() {
wrapper.stopTask();
boolean wasFirstStop = firstStop == false;
firstStop = true;
return wasFirstStop;
}
}
/**
* The monitor runnable which validates that threads are completing within
* the task completion timeout limits.
*/
private class TimeoutMonitor implements Runnable {
final Logger log;
TimeoutMonitor(String name, Logger log) {
this.log = log;
Thread t = new Thread(this, name + " TimeoutMonitor");
t.setDaemon(true);
t.start();
}
/** The monitor thread loops until the pool is shutdown. It waits for
* tasks with completion timeouts and sleeps until the next completion
* timeout and then interrupts the associated task thread, and invokes
* stopTask on the TaskWrapper. A new timeout check is then inserted with
* a 1 second timeout to validate that the TaskWrapper has exited the
* run method. If it has not, then the associated task thread is stopped
* using the deprecated Thread.stop method since this is the only way to
* abort a thread that is in spin loop for example.
*
* @todo this is not responsive to new tasks with timeouts smaller than
* the current shortest completion expiration. We probably should interrupt
* the thread on each insertion into the timeout heap to ensure better
* responsiveness.
*/
public void run() {
boolean isStopped = stopped.get();
while (isStopped == false) {
try {
TimeoutInfo info = getNextTimeout();
if (info != null) {
long now = System.currentTimeMillis();
long timeToTimeout = info.getTaskCompletionTimeout(now);
if (timeToTimeout > 0) {
if (log.isDebugEnabled()) {
log.debug("Will check wrapper=" + info.getTaskWrapper() + " after "
+ timeToTimeout);
}
Thread.sleep(timeToTimeout);
}
// Check the status of the task
TaskWrapper wrapper = info.getTaskWrapper();
if (wrapper.isComplete() == false) {
if (log.isDebugEnabled()) {
log.debug("Failed completion check for wrapper=" + wrapper);
}
if (info.stopTask() == true) {
// Requeue the TimeoutInfo to see that the task exits run
info.setTimeout(1000);
tasksWithTimeouts.insert(info);
if (log.isDebugEnabled()) {
log
.debug("Rescheduled completion check for wrapper="
+ wrapper);
}
}
}
} else {
Thread.sleep(1000);
}
} catch (InterruptedException e) {
if (log.isDebugEnabled()) {
log.debug("Timeout monitor has been interrupted", e);
}
} catch (Throwable e) {
if (log.isDebugEnabled()) {
log.debug("Timeout monitor saw unexpected error", e);
}
}
isStopped = stopped.get();
}
}
}
}