/* * Copyright 2001-2009 Terracotta, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * */ package org.quartz.simpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.SchedulerConfigException; import org.quartz.spi.ThreadPool; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** * <p> * This is class is a simple implementation of a thread pool, based on the * <code>{@link org.quartz.spi.ThreadPool}</code> interface. * </p> * * <p> * <CODE>Runnable</CODE> objects are sent to the pool with the <code>{@link #runInThread(Runnable)}</code> * method, which blocks until a <code>Thread</code> becomes available. * </p> * * <p> * The pool has a fixed number of <code>Thread</code>s, and does not grow or * shrink based on demand. * </p> * * @author James House * @author Juergen Donnerstag */ public class SimpleThreadPool implements ThreadPool { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private int count = -1; private int prio = Thread.NORM_PRIORITY; private boolean isShutdown = false; private boolean handoffPending = false; private boolean inheritLoader = false; private boolean inheritGroup = true; private boolean makeThreadsDaemons = false; private ThreadGroup threadGroup; private final Object nextRunnableLock = new Object(); private List<WorkerThread> workers; private LinkedList<WorkerThread> availWorkers = new LinkedList<WorkerThread>(); private LinkedList<WorkerThread> busyWorkers = new LinkedList<WorkerThread>(); private String threadNamePrefix; private final Logger log = LoggerFactory.getLogger(getClass()); private String schedulerInstanceName; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * <p> * Create a new (unconfigured) <code>SimpleThreadPool</code>. * </p> * * @see #setThreadCount(int) * @see #setThreadPriority(int) */ public SimpleThreadPool() { } /** * <p> * Create a new <code>SimpleThreadPool</code> with the specified number * of <code>Thread</code> s that have the given priority. * </p> * * @param threadCount * the number of worker <code>Threads</code> in the pool, must * be > 0. * @param threadPriority * the thread priority for the worker threads. * * @see java.lang.Thread */ public SimpleThreadPool(int threadCount, int threadPriority) { setThreadCount(threadCount); setThreadPriority(threadPriority); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public Logger getLog() { return log; } public int getPoolSize() { return getThreadCount(); } /** * <p> * Set the number of worker threads in the pool - has no effect after * <code>initialize()</code> has been called. * </p> */ public void setThreadCount(int count) { this.count = count; } /** * <p> * Get the number of worker threads in the pool. * </p> */ public int getThreadCount() { return count; } /** * <p> * Set the thread priority of worker threads in the pool - has no effect * after <code>initialize()</code> has been called. * </p> */ public void setThreadPriority(int prio) { this.prio = prio; } /** * <p> * Get the thread priority of worker threads in the pool. * </p> */ public int getThreadPriority() { return prio; } public void setThreadNamePrefix(String prfx) { this.threadNamePrefix = prfx; } public String getThreadNamePrefix() { return threadNamePrefix; } /** * @return Returns the * threadsInheritContextClassLoaderOfInitializingThread. */ public boolean isThreadsInheritContextClassLoaderOfInitializingThread() { return inheritLoader; } /** * @param inheritLoader * The threadsInheritContextClassLoaderOfInitializingThread to * set. */ public void setThreadsInheritContextClassLoaderOfInitializingThread( boolean inheritLoader) { this.inheritLoader = inheritLoader; } public boolean isThreadsInheritGroupOfInitializingThread() { return inheritGroup; } public void setThreadsInheritGroupOfInitializingThread( boolean inheritGroup) { this.inheritGroup = inheritGroup; } /** * @return Returns the value of makeThreadsDaemons. */ public boolean isMakeThreadsDaemons() { return makeThreadsDaemons; } /** * @param makeThreadsDaemons * The value of makeThreadsDaemons to set. */ public void setMakeThreadsDaemons(boolean makeThreadsDaemons) { this.makeThreadsDaemons = makeThreadsDaemons; } public void setInstanceId(String schedInstId) { } public void setInstanceName(String schedName) { schedulerInstanceName = schedName; } public void initialize() throws SchedulerConfigException { if(workers != null && workers.size() > 0) // already initialized... return; if (count <= 0) { throw new SchedulerConfigException( "Thread count must be > 0"); } if (prio <= 0 || prio > 9) { throw new SchedulerConfigException( "Thread priority must be > 0 and <= 9"); } if(isThreadsInheritGroupOfInitializingThread()) { threadGroup = Thread.currentThread().getThreadGroup(); } else { // follow the threadGroup tree to the root thread group. threadGroup = Thread.currentThread().getThreadGroup(); ThreadGroup parent = threadGroup; while ( !parent.getName().equals("main") ) { threadGroup = parent; parent = threadGroup.getParent(); } threadGroup = new ThreadGroup(parent, schedulerInstanceName + "-SimpleThreadPool"); if (isMakeThreadsDaemons()) { threadGroup.setDaemon(true); } } if (isThreadsInheritContextClassLoaderOfInitializingThread()) { getLog().info( "Job execution threads will use class loader of thread: " + Thread.currentThread().getName()); } // create the worker threads and start them Iterator<WorkerThread> workerThreads = createWorkerThreads(count).iterator(); while(workerThreads.hasNext()) { WorkerThread wt = workerThreads.next(); wt.start(); availWorkers.add(wt); } } protected List<WorkerThread> createWorkerThreads(int createCount) { workers = new LinkedList<WorkerThread>(); for (int i = 1; i<= createCount; ++i) { String threadPrefix = getThreadNamePrefix(); if (threadPrefix == null) { threadPrefix = schedulerInstanceName + "_Worker"; } WorkerThread wt = new WorkerThread(this, threadGroup, threadPrefix + "-" + i, getThreadPriority(), isMakeThreadsDaemons()); if (isThreadsInheritContextClassLoaderOfInitializingThread()) { wt.setContextClassLoader(Thread.currentThread() .getContextClassLoader()); } workers.add(wt); } return workers; } /** * <p> * Terminate any worker threads in this thread group. * </p> * * <p> * Jobs currently in progress will complete. * </p> */ public void shutdown() { shutdown(true); } /** * <p> * Terminate any worker threads in this thread group. * </p> * * <p> * Jobs currently in progress will complete. * </p> */ public void shutdown(boolean waitForJobsToComplete) { synchronized (nextRunnableLock) { getLog().debug("Shutting down threadpool..."); isShutdown = true; if(workers == null) // case where the pool wasn't even initialize()ed return; // signal each worker thread to shut down Iterator<WorkerThread> workerThreads = workers.iterator(); while(workerThreads.hasNext()) { WorkerThread wt = workerThreads.next(); wt.shutdown(); availWorkers.remove(wt); } // Give waiting (wait(1000)) worker threads a chance to shut down. // Active worker threads will shut down after finishing their // current job. nextRunnableLock.notifyAll(); if (waitForJobsToComplete == true) { boolean interrupted = false; try { // wait for hand-off in runInThread to complete... while(handoffPending) { try { nextRunnableLock.wait(100); } catch(InterruptedException _) { interrupted = true; } } // Wait until all worker threads are shut down while (busyWorkers.size() > 0) { WorkerThread wt = (WorkerThread) busyWorkers.getFirst(); try { getLog().debug( "Waiting for thread " + wt.getName() + " to shut down"); // note: with waiting infinite time the // application may appear to 'hang'. nextRunnableLock.wait(2000); } catch (InterruptedException _) { interrupted = true; } } workerThreads = workers.iterator(); while(workerThreads.hasNext()) { WorkerThread wt = (WorkerThread) workerThreads.next(); try { wt.join(); workerThreads.remove(); } catch (InterruptedException _) { interrupted = true; } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } getLog().debug("No executing jobs remaining, all threads stopped."); } getLog().debug("Shutdown of threadpool complete."); } } /** * <p> * Run the given <code>Runnable</code> object in the next available * <code>Thread</code>. If while waiting the thread pool is asked to * shut down, the Runnable is executed immediately within a new additional * thread. * </p> * * @param runnable * the <code>Runnable</code> to be added. */ public boolean runInThread(Runnable runnable) { if (runnable == null) { return false; } synchronized (nextRunnableLock) { handoffPending = true; // Wait until a worker thread is available while ((availWorkers.size() < 1) && !isShutdown) { try { nextRunnableLock.wait(500); } catch (InterruptedException ignore) { } } if (!isShutdown) { WorkerThread wt = (WorkerThread)availWorkers.removeFirst(); busyWorkers.add(wt); wt.run(runnable); } else { // If the thread pool is going down, execute the Runnable // within a new additional worker thread (no thread from the pool). WorkerThread wt = new WorkerThread(this, threadGroup, "WorkerThread-LastJob", prio, isMakeThreadsDaemons(), runnable); busyWorkers.add(wt); workers.add(wt); wt.start(); } nextRunnableLock.notifyAll(); handoffPending = false; } return true; } public int blockForAvailableThreads() { synchronized(nextRunnableLock) { while((availWorkers.size() < 1 || handoffPending) && !isShutdown) { try { nextRunnableLock.wait(500); } catch (InterruptedException ignore) { } } return availWorkers.size(); } } protected void makeAvailable(WorkerThread wt) { synchronized(nextRunnableLock) { if(!isShutdown) { availWorkers.add(wt); } busyWorkers.remove(wt); nextRunnableLock.notifyAll(); } } protected void clearFromBusyWorkersList(WorkerThread wt) { synchronized(nextRunnableLock) { busyWorkers.remove(wt); nextRunnableLock.notifyAll(); } } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * WorkerThread Class. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * <p> * A Worker loops, waiting to execute tasks. * </p> */ class WorkerThread extends Thread { private final Object lock = new Object(); // A flag that signals the WorkerThread to terminate. private AtomicBoolean run = new AtomicBoolean(true); private SimpleThreadPool tp; private Runnable runnable = null; private boolean runOnce = false; /** * <p> * Create a worker thread and start it. Waiting for the next Runnable, * executing it, and waiting for the next Runnable, until the shutdown * flag is set. * </p> */ WorkerThread(SimpleThreadPool tp, ThreadGroup threadGroup, String name, int prio, boolean isDaemon) { this(tp, threadGroup, name, prio, isDaemon, null); } /** * <p> * Create a worker thread, start it, execute the runnable and terminate * the thread (one time execution). * </p> */ WorkerThread(SimpleThreadPool tp, ThreadGroup threadGroup, String name, int prio, boolean isDaemon, Runnable runnable) { super(threadGroup, name); this.tp = tp; this.runnable = runnable; if(runnable != null) runOnce = true; setPriority(prio); setDaemon(isDaemon); } /** * <p> * Signal the thread that it should terminate. * </p> */ void shutdown() { run.set(false); } public void run(Runnable newRunnable) { synchronized(lock) { if(runnable != null) { throw new IllegalStateException("Already running a Runnable!"); } runnable = newRunnable; lock.notifyAll(); } } /** * <p> * Loop, executing targets as they are received. * </p> */ @Override public void run() { boolean ran = false; while (run.get()) { try { synchronized(lock) { while (runnable == null && run.get()) { lock.wait(500); } if (runnable != null) { ran = true; runnable.run(); } } } catch (InterruptedException unblock) { // do nothing (loop will terminate if shutdown() was called try { getLog().error("Worker thread was interrupt()'ed.", unblock); } catch(Exception e) { // ignore to help with a tomcat glitch } } catch (Throwable exceptionInRunnable) { try { getLog().error("Error while executing the Runnable: ", exceptionInRunnable); } catch(Exception e) { // ignore to help with a tomcat glitch } } finally { synchronized(lock) { runnable = null; } // repair the thread in case the runnable mucked it up... if(getPriority() != tp.getThreadPriority()) { setPriority(tp.getThreadPriority()); } if (runOnce) { run.set(false); clearFromBusyWorkersList(this); } else if(ran) { ran = false; makeAvailable(this); } } } //if (log.isDebugEnabled()) try { getLog().debug("WorkerThread is shut down."); } catch(Exception e) { // ignore to help with a tomcat glitch } } } }