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

com.helger.quartz.simpl.SimpleThreadPool Maven / Gradle / Ivy

/**
 * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
 *
 * Copyright (C) 2016-2020 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.quartz.simpl;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.quartz.SchedulerConfigException;
import com.helger.quartz.spi.IThreadPool;

/**
 * 

* This is class is a simple implementation of a thread pool, based on the * {@link com.helger.quartz.spi.IThreadPool} interface. *

*

* Runnable objects are sent to the pool with the * {@link #runInThread(Runnable)} method, which blocks until a * Thread becomes available. *

*

* The pool has a fixed number of Threads, and does not grow or * shrink based on demand. *

* * @author James House * @author Juergen Donnerstag */ public class SimpleThreadPool implements IThreadPool { private static final Logger LOGGER = LoggerFactory.getLogger (SimpleThreadPool.class); private int m_nCount = -1; private int m_nPrio = Thread.NORM_PRIORITY; private boolean m_bIsShutdown = false; private boolean m_bHandoffPending = false; private boolean m_bInheritLoader = false; private boolean m_bInheritGroup = true; private boolean m_bMakeThreadsDaemons = false; private ThreadGroup m_aThreadGroup; private final Object m_aNextRunnableLock = new Object (); private ICommonsList m_aWorkers; private final ICommonsList m_aAvailWorkers = new CommonsArrayList <> (); private final ICommonsList m_aBusyWorkers = new CommonsArrayList <> (); private String m_sThreadNamePrefix; private String m_sSchedulerInstanceName; /** * Create a new (unconfigured) SimpleThreadPool. * * @see #setThreadCount(int) * @see #setThreadPriority(int) */ public SimpleThreadPool () {} /** *

* Create a new SimpleThreadPool with the specified number of * Thread s that have the given priority. *

* * @param threadCount * the number of worker Threads in the pool, must be > * 0. * @param threadPriority * the thread priority for the worker threads. * @see java.lang.Thread */ public SimpleThreadPool (final int threadCount, final int threadPriority) { setThreadCount (threadCount); setThreadPriority (threadPriority); } public int getPoolSize () { return getThreadCount (); } /** *

* Set the number of worker threads in the pool - has no effect after * initialize() has been called. *

*/ public final void setThreadCount (final int count) { m_nCount = count; } /** *

* Get the number of worker threads in the pool. *

*/ public final int getThreadCount () { return m_nCount; } /** *

* Set the thread priority of worker threads in the pool - has no effect after * initialize() has been called. *

*/ public final void setThreadPriority (final int prio) { m_nPrio = prio; } /** *

* Get the thread priority of worker threads in the pool. *

*/ public final int getThreadPriority () { return m_nPrio; } public final void setThreadNamePrefix (@Nullable final String prfx) { m_sThreadNamePrefix = prfx; } @Nullable public final String getThreadNamePrefix () { return m_sThreadNamePrefix; } /** * @return Returns the threadsInheritContextClassLoaderOfInitializingThread. */ public final boolean isThreadsInheritContextClassLoaderOfInitializingThread () { return m_bInheritLoader; } /** * @param inheritLoader * The threadsInheritContextClassLoaderOfInitializingThread to set. */ public final void setThreadsInheritContextClassLoaderOfInitializingThread (final boolean inheritLoader) { m_bInheritLoader = inheritLoader; } public final boolean isThreadsInheritGroupOfInitializingThread () { return m_bInheritGroup; } public final void setThreadsInheritGroupOfInitializingThread (final boolean inheritGroup) { m_bInheritGroup = inheritGroup; } /** * @return Returns the value of makeThreadsDaemons. */ public final boolean isMakeThreadsDaemons () { return m_bMakeThreadsDaemons; } /** * @param makeThreadsDaemons * The value of makeThreadsDaemons to set. */ public final void setMakeThreadsDaemons (final boolean makeThreadsDaemons) { m_bMakeThreadsDaemons = makeThreadsDaemons; } public void setInstanceId (final String schedInstId) {} public void setInstanceName (final String schedName) { m_sSchedulerInstanceName = schedName; } public void initialize () throws SchedulerConfigException { // already initialized... if (m_aWorkers != null && m_aWorkers.isNotEmpty ()) return; if (m_nCount <= 0) throw new SchedulerConfigException ("Thread count must be > 0"); if (m_nPrio < Thread.MIN_PRIORITY || m_nPrio > Thread.MAX_PRIORITY) throw new SchedulerConfigException ("Thread priority must be <= " + Thread.MIN_PRIORITY + " and <= " + Thread.MAX_PRIORITY); if (isThreadsInheritGroupOfInitializingThread ()) { m_aThreadGroup = Thread.currentThread ().getThreadGroup (); } else { // follow the threadGroup tree to the root thread group. m_aThreadGroup = Thread.currentThread ().getThreadGroup (); ThreadGroup aParent = m_aThreadGroup; while (!aParent.getName ().equals ("main")) { m_aThreadGroup = aParent; aParent = m_aThreadGroup.getParent (); if (aParent == null) throw new IllegalStateException ("Failed to resolve thread group with name 'main'"); } m_aThreadGroup = new ThreadGroup (aParent, m_sSchedulerInstanceName + "-SimpleThreadPool"); if (isMakeThreadsDaemons ()) m_aThreadGroup.setDaemon (true); } if (isThreadsInheritContextClassLoaderOfInitializingThread ()) { LOGGER.info ("Job execution threads will use class loader of thread: " + Thread.currentThread ().getName ()); } // create the worker threads and start them final Iterator workerThreads = createWorkerThreads (m_nCount).iterator (); while (workerThreads.hasNext ()) { final WorkerThread wt = workerThreads.next (); wt.start (); m_aAvailWorkers.add (wt); } LOGGER.info ("Initialized " + m_nCount + " worker threads"); } protected List createWorkerThreads (final int createCount) { m_aWorkers = new CommonsArrayList <> (); for (int i = 1; i <= createCount; ++i) { String sThreadPrefix = getThreadNamePrefix (); if (sThreadPrefix == null) { sThreadPrefix = m_sSchedulerInstanceName + "_Worker"; } final WorkerThread wt = new WorkerThread (this, m_aThreadGroup, sThreadPrefix + "-" + i, getThreadPriority (), isMakeThreadsDaemons ()); if (isThreadsInheritContextClassLoaderOfInitializingThread ()) { wt.setContextClassLoader (Thread.currentThread ().getContextClassLoader ()); } m_aWorkers.add (wt); } return m_aWorkers; } /** *

* Terminate any worker threads in this thread group. *

*

* Jobs currently in progress will complete. *

*/ public void shutdown () { shutdown (true); } /** *

* Terminate any worker threads in this thread group. *

*

* Jobs currently in progress will complete. *

*/ public void shutdown (final boolean waitForJobsToComplete) { synchronized (m_aNextRunnableLock) { if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Shutting down threadpool..."); m_bIsShutdown = true; if (m_aWorkers == null) // case where the pool wasn't even initialize()ed return; // signal each worker thread to shut down Iterator workerThreads = m_aWorkers.iterator (); while (workerThreads.hasNext ()) { final WorkerThread wt = workerThreads.next (); wt.shutdown (); m_aAvailWorkers.remove (wt); } // Give waiting (wait(1000)) worker threads a chance to shut down. // Active worker threads will shut down after finishing their // current job. m_aNextRunnableLock.notifyAll (); if (waitForJobsToComplete) { boolean interrupted = false; try { // wait for hand-off in runInThread to complete... while (m_bHandoffPending) { try { m_aNextRunnableLock.wait (100); } catch (final InterruptedException ex) { interrupted = true; } } // Wait until all worker threads are shut down while (m_aBusyWorkers.isNotEmpty ()) { final WorkerThread wt = m_aBusyWorkers.get (0); try { if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Waiting for thread " + wt.getName () + " to shut down"); // note: with waiting infinite time the // application may appear to 'hang'. m_aNextRunnableLock.wait (2000); } catch (final InterruptedException ex) { interrupted = true; } } workerThreads = m_aWorkers.iterator (); while (workerThreads.hasNext ()) { final WorkerThread wt = workerThreads.next (); try { wt.join (); workerThreads.remove (); } catch (final InterruptedException ex) { interrupted = true; } } } finally { if (interrupted) { Thread.currentThread ().interrupt (); } } if (LOGGER.isDebugEnabled ()) LOGGER.debug ("No executing jobs remaining, all threads stopped."); } if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Shutdown of threadpool complete."); } } /** *

* Run the given Runnable object in the next available * Thread. If while waiting the thread pool is asked to shut * down, the Runnable is executed immediately within a new additional thread. *

* * @param aRunnable * the Runnable to be added. */ public boolean runInThread (final Runnable aRunnable) { if (aRunnable == null) return false; synchronized (m_aNextRunnableLock) { m_bHandoffPending = true; // Wait until a worker thread is available while (m_aAvailWorkers.isEmpty () && !m_bIsShutdown) { try { m_aNextRunnableLock.wait (500); } catch (final InterruptedException ignore) { Thread.currentThread ().interrupt (); } } if (!m_bIsShutdown) { final WorkerThread wt = m_aAvailWorkers.remove (0); m_aBusyWorkers.add (wt); wt.run (aRunnable); } else { // If the thread pool is going down, execute the Runnable // within a new additional worker thread (no thread from the pool). final WorkerThread wt = new WorkerThread (this, m_aThreadGroup, "WorkerThread-LastJob", m_nPrio, isMakeThreadsDaemons (), aRunnable); m_aBusyWorkers.add (wt); m_aWorkers.add (wt); wt.start (); } m_aNextRunnableLock.notifyAll (); m_bHandoffPending = false; } return true; } public int blockForAvailableThreads () { synchronized (m_aNextRunnableLock) { while ((m_aAvailWorkers.isEmpty () || m_bHandoffPending) && !m_bIsShutdown) { try { m_aNextRunnableLock.wait (500); } catch (final InterruptedException ignore) { Thread.currentThread ().interrupt (); } } return m_aAvailWorkers.size (); } } protected void makeAvailable (final WorkerThread wt) { synchronized (m_aNextRunnableLock) { if (!m_bIsShutdown) { m_aAvailWorkers.add (wt); } m_aBusyWorkers.remove (wt); m_aNextRunnableLock.notifyAll (); } } protected void clearFromBusyWorkersList (final WorkerThread wt) { synchronized (m_aNextRunnableLock) { m_aBusyWorkers.remove (wt); m_aNextRunnableLock.notifyAll (); } } /** * A Worker loops, waiting to execute tasks. */ final class WorkerThread extends Thread { private final Object m_aLock = new Object (); // A flag that signals the WorkerThread to terminate. private final AtomicBoolean m_aCanRun = new AtomicBoolean (true); private final SimpleThreadPool m_aSTP; private Runnable m_aRunnable; private boolean m_bRunOnce = false; /* * 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. */ WorkerThread (final SimpleThreadPool aSTP, final ThreadGroup aThreadGroup, final String sName, final int nPrio, final boolean bIsDaemon) { this (aSTP, aThreadGroup, sName, nPrio, bIsDaemon, null); } /* * Create a worker thread, start it, execute the runnable and terminate the * thread (one time execution). */ WorkerThread (final SimpleThreadPool aSTP, final ThreadGroup aThreadGroup, final String sName, final int nPrio, final boolean bIsDaemon, final Runnable aRunnable) { super (aThreadGroup, sName); m_aSTP = aSTP; m_aRunnable = aRunnable; if (aRunnable != null) m_bRunOnce = true; setPriority (nPrio); setDaemon (bIsDaemon); } /** *

* Signal the thread that it should terminate. *

*/ void shutdown () { m_aCanRun.set (false); } public void run (final Runnable aNewRunnable) { synchronized (m_aLock) { if (m_aRunnable != null) throw new IllegalStateException ("Already running a Runnable!"); m_aRunnable = aNewRunnable; m_aLock.notifyAll (); } } /** *

* Loop, executing targets as they are received. *

*/ @Override public void run () { boolean bRan = false; while (m_aCanRun.get ()) { try { synchronized (m_aLock) { while (m_aRunnable == null && m_aCanRun.get ()) { m_aLock.wait (500); } if (m_aRunnable != null) { bRan = true; m_aRunnable.run (); } } } catch (final InterruptedException unblock) { // do nothing (loop will terminate if shutdown() was called try { LOGGER.error ("Worker thread was interrupt'ed.", unblock); } catch (final Exception e) { // ignore to help with a tomcat glitch } Thread.currentThread ().interrupt (); } catch (final Exception exceptionInRunnable) { try { LOGGER.error ("Error while executing the Runnable: ", exceptionInRunnable); } catch (final Exception e) { // ignore to help with a tomcat glitch } } finally { synchronized (m_aLock) { m_aRunnable = null; } // repair the thread in case the runnable mucked it up... if (getPriority () != m_aSTP.getThreadPriority ()) { setPriority (m_aSTP.getThreadPriority ()); } if (m_bRunOnce) { m_aCanRun.set (false); clearFromBusyWorkersList (this); } else if (bRan) { bRan = false; makeAvailable (this); } } } try { if (LOGGER.isDebugEnabled ()) LOGGER.debug ("WorkerThread is shut down."); } catch (final Exception e) { // ignore to help with a tomcat glitch } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy