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

net.sf.eBus.util.EventThread Maven / Gradle / Ivy

There is a newer version: 7.4.0
Show newest version
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright (C) 2001 - 2009, 2013. Charles W. Rapp.
// All Rights Reserved.
//

package net.sf.eBus.util;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A Java thread with a
 * {@link net.sf.eBus.util.EventThread#run()} method that
 * iterates over a Java {@code BlockingQueue}, processing
 * each event in the same order as posted. The event thread has
 * three general states:
 * 
    *
  1. * Starting: The subclass' * {@link net.sf.eBus.util.EventThread#starting()} method * is called to perform the actual initialization. If this * method returns {@code true}, the thread moves to the * next state. If {@code false} is returned or this method * throws an exception, the event thread stops running. *
  2. *
  3. * Running: The thread continues taking enqueued * events and passing them to the the subclass' * {@link net.sf.eBus.util.EventThread#handleEvent(Object)} * method. This continues until the thread is halted. * If {@link net.sf.eBus.util.EventThread#haltNow(boolean)} * is called, all enqueued events are discarded and the * event thread immediately goes to the next state. If * {@link net.sf.eBus.util.EventThread#halt(boolean)} is * called, a special halt event is enqueued. The event * thread continues processing events until the halt event * is reached. The event thread then goes to the next * state. In either case, after the event thread is halted, * no more events may be enqueued. *
  4. *
  5. * Stopping: The event thread concludes its run * by calling the subclass' * {@link net.sf.eBus.util.EventThread#stopping()} method. * The subclass performs the necessary clean up before * the event thread stops running. *
  6. *
* This class is thread safe. There is no need to externally * synchronize an event thread before calling any of its methods. *

* The event thread supports only a First In, First Out (FIFO) * blocking queue with configurable capacity. There is no support * for a priority blocking queue. * * @author Charles Rapp */ public abstract class EventThread extends Thread { //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Constructs an {@code EventThread} with a capacity of * {@code Integer.MAX_VALUE}. The thread name is set to * "EventThread_n" where n is an arbitrary * number. */ protected EventThread() { this (String.format(NAME_PREFIX, _threadNumber.getAndIncrement()), Integer.MAX_VALUE); } // end of EventThread() /** * Constructs an {@code EventThread} with a specified * thread name and a default capacity of * {@code Integer.MAX_VALUE}. * @param name Thread name. */ protected EventThread(final String name) { this (name, Integer.MAX_VALUE); } // end of EventThread(String) /** * Constructs a {@code EventThread} with a specified * event queue capacity. The thread name is set to * "EventThread_n" where n is an arbitrary * number. * @param capacity Event queue capacity. * @exception IllegalArgumentException * if {@code capacity} is <= zero. */ protected EventThread(final int capacity) { this (String.format(NAME_PREFIX, _threadNumber.getAndIncrement()), capacity); } // end of EventThread(int) /** * Constructs an {@code EventThread} with a specified * name and queue capacity. * @param name Thread name. * @param capacity Event queue capacity. * @exception IllegalArgumentException * if {@code capacity} is <= zero. */ protected EventThread(final String name, final int capacity) { super (name); if (capacity <= 0) { throw ( new IllegalArgumentException( "invalid capacity (" + Integer.toString(capacity) + ")")); } else { _runState = new AtomicReference<>(RunState.NOT_STARTED); _queue = new LinkedBlockingQueue<>(capacity); _startSignal = new CountDownLatch(1); _stopSignal = new CountDownLatch(1); } // Accept default null, 0 and false settings for // remaining member data. } // end of EventThread(String, int) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Get methods. // /** * Returns {@code true} if this event thread has been * halted and {@code false} otherwise. It is possible * for this method to return {@code true} yet still * receive calls to {@link #handleEvent(Object)}. This * will happen when {@link #halt(boolean)} is called, the * event queue is not empty and so the thread is still * processing events. *

* Use {@link #runstate()} for finer-grained determination * of the event thread's current state. *

* Call {@link #haltNow(boolean)} to stop the event thread * immediately despite the presence of unprocessed events. * @return {@code true} if this event thread has been * halted and {@code false} otherwise. * @see #halt(boolean) * @see #haltNow(boolean) * @see #runstate() */ public final boolean isHalted() { return ((_runState.get()).compareTo( RunState.HALT_DRAIN) >= 0); } // end of isHalted() /** * Returns this event thread current * {@link EventThread.RunState state}. * @return this event thread current * {@link EventThread.RunState state}. * @see #isHalted() */ public final RunState runstate() { return (_runState.get()); } // end of runstate() // // end of Get methods. //----------------------------------------------------------- //----------------------------------------------------------- // Thread Method Overrides. // /** * The main {@code Thread} routine. *

* Do not call this method directly. Instead, call * the {@code start} method. */ @Override @SuppressWarnings ("unchecked") public final void run() { boolean startFlag = false; Object event; if (_logger.isLoggable(Level.FINER) == true) { _logger.log( Level.FINER, "{0} is starting.", getName()); } // halt() needs to know what thread it is running in. _thread = Thread.currentThread(); // Give the derived class a chance to prepare for start. try { if (_runState.get() != RunState.NOT_STARTED) { // no-op. This thread was halted before it got // a chance to start. } else if ((startFlag = starting()) == true) { if (_logger.isLoggable(Level.FINE) == true) { _logger.log( Level.FINE, "{0} started.", getName()); } _runState.set(RunState.RUNNING); } else { _runState.set(RunState.HALTED); } } catch (Exception jex) { _logger.log(Level.WARNING, "thread exception while starting.", jex); _runState.set(RunState.HALTED); } // One way or the other, this thread is "started". // Decrement the start signal and let start(boolean) // know about it. _startSignal.countDown(); if (_runState.get() == RunState.RUNNING && _logger.isLoggable(Level.FINE) == true) { _logger.log( Level.FINE, "{0} is now running.", getName()); } // Keep running until halted. while ((_runState.get()).compareTo( RunState.HALT_DRAIN) <= 0) { try { // Since a queue is FIFO (first in, // first out), always remove the // queue's first entry and always add // new entries to the back. event = _queue.take(); // If this is a halt event, then halt. if (event instanceof HaltEvent) { _runState.set(RunState.HALT_NOW); } else { handleEvent(event); } } catch (InterruptedException interrupt) { // Ignore. } catch (Exception threadex) { _logger.log( Level.WARNING, "Event processing failure.", threadex); } } _thread = null; // Allow the derived class to clean up before stopping // and informing the listener. // But do not bother stopping if this thread did no start // cleanly. if (startFlag == true) { if (_logger.isLoggable(Level.FINER) == true) { _logger.log( Level.FINER, "{0} is stopping.", getName()); } try { _runState.set(RunState.STOPPING); stopping(); } catch (Exception jex) { _logger.log( Level.WARNING, "thread exception while stopping", jex); } _runState.set(RunState.HALTED); if (_logger.isLoggable(Level.FINE) == true) { _logger.log( Level.FINE, "{0} stopped.", getName()); } // Again, let halt() or haltNow() know we are // finished in case they are waiting. _stopSignal.countDown(); } return; } // end of run() // // end of Thread Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Declarations. // /** * Processes the next enqueued event. * @param event the next event. */ public abstract void handleEvent(Object event); /** * Returns {@code true} if the {@code EventThread} * subclass initializes successfully and {@code false} * otherwise. If {@code false} is returned or throws an * exception, then this event thread immediately halts. * @return {@code true} if the {@code EventThread} * subclass initializes successfully and {@code false} * otherwise. */ public abstract boolean starting(); /** * Performs any necessary local clean up when the thread * halts. */ public abstract void stopping(); // // end of Abstract Method Declarations. //----------------------------------------------------------- /** * Starts the thread execution. If {@code waitFlag} is * {@code true}, then waits for this thread to begin * execution before return; otherwise returns immediately. *

* Note: waiting for this event thread to start * does not imply this thread is running, only that its * {@link #starting} method has completed. If * {@link #starting} returns {@code false}, then this * thread will immediately terminate. Call {@link #runstate} * to determine if this thread is * {@link RunState#RUNNING running}. * @param waitFlag if {@code true}, then wait for the event * thread to start before returning; otherwise return * immediately. * @exception IllegalStateException * if this thread was previously started. */ public final void start(final boolean waitFlag) throws IllegalStateException { if (_runState.get() != RunState.NOT_STARTED) { throw ( new IllegalStateException( "thread previously started")); } // Start this thread running. super.start(); if (waitFlag == true) { try { _startSignal.await(); } catch (InterruptedException interrupt) { // Ignore. } } // Drop the start signal as it is no longer needed. _startSignal = null; return; } // end of start(boolean) /** * Appends an event to the queue. If the queue's * capacity is full, this method will wait until * the event can be enqueued. * @param event append this event to the queue. * @exception IllegalArgumentException * if {@code event} is {@code null}. * @exception IllegalStateException * if this event thread is halted. */ public final void add(final Object event) throws IllegalArgumentException, IllegalStateException { final RunState runState = _runState.get(); if (event == null) { throw (new IllegalArgumentException("null event")); } else if (runState == RunState.HALT_DRAIN || runState == RunState.HALT_NOW) { throw ( new IllegalStateException( "thread halted")); } else { try { _queue.put(event); } catch (InterruptedException interrupt) { // Interrupts are used to halt event // threads. If the thread is being // halted, then don't worry about // enqueuing new events. } } return; } // end of add(Object) /** * Permanently stops this thread's execution after * the event queue is empty. Subsequent calls to * {@link #add(Object)} and * {@code java.lang.IllegalStateException}. *

* If {@code waitFlag} is {@code true}, then this method * blocks until this event thread has finished processing * all extant events and is halted. *

* Does nothing if this thread was previously halted and * returns immediately. * @param waitFlag if {@code true}, then wait for the event * thread to halt before returning; otherwise return * immediately. */ public final void halt(final boolean waitFlag) { final RunState runState = _runState.get(); if (runState == RunState.STARTING || runState == RunState.RUNNING) { if (_logger.isLoggable(Level.FINER) == true) { _logger.log( Level.FINER, "Halting {0}.", getName()); } _runState.set(RunState.HALT_DRAIN); try { _queue.put(new HaltEvent()); // If we are to wait, then do so. if (waitFlag == true) { _stopSignal.await(); } } catch (InterruptedException interrupt) { // Interrupts are used to halt event threads. // If the thread is being halted, then don't // worry about enqueuing new events. // Go to the HALT_NOW state instead. _runState.set(RunState.HALT_NOW); _thread.interrupt(); } } return; } // end of halt(boolean) /** * Permanently stops this thread's execution now. * All events currently equeued and not yet processed are * cleared from the queue and will not be processed. * Subsequent calls to {@link #add(Object)} and * {@code java.lang.IllegalStateException}. *

* If {@code waitFlag} is {@code true}, then this method * blocks until this event thread is halted. *

* Does nothing if this thread was previously halted and * returns immediately. * @param waitFlag if {@code true}, then wait for the event * thread to halt before returning; otherwise return * immediately. */ public final void haltNow(final boolean waitFlag) { final RunState runState = _runState.get(); if (runState == RunState.STARTING || runState == RunState.RUNNING) { if (_logger.isLoggable(Level.FINER) == true) { _logger.log(Level.FINER, "Halting {0} immediately.", getName()); } _runState.set(RunState.HALT_NOW); _thread.interrupt(); } // If we are to wait, then do so. if (waitFlag == true) { try { _stopSignal.await(); } catch (InterruptedException interrupt) { // Ignore. } } return; } // end of haltNow(boolean) //--------------------------------------------------------------- // Member data. // /** * Continue running this thread while this state is * {@link RunState#RUNNING}. In the * {@link RunState#HALT_DRAIN} state, keep running until the * queue is empty (no more events can be added). * In the {@link RunState#HALT_NOW} state, stop running * immediately. */ private AtomicReference _runState; /** * Enqueue new elements here. */ private BlockingQueue _queue; /** * halt() needs to know which thread to interrupt. */ private Thread _thread; /** * If {@link #start} needs to wait, then use this count down * latch to coordinate the thread starting. */ private CountDownLatch _startSignal; /** * If {@link #halt} needs to wait, then use this count down * latch to coordinate the thread stopping. */ private CountDownLatch _stopSignal; //----------------------------------------------------------- // Statics. // /** * Use this to generate unique default event thread names. */ private static AtomicInteger _threadNumber = new AtomicInteger(); /** * The logging subsystem interface. */ private static final Logger _logger = Logger.getLogger(EventThread.class.getName()); //----------------------------------------------------------- // Constants. // /** * Use this string to generate the event thread name. */ private static final String NAME_PREFIX = "EventThread_%d"; //--------------------------------------------------------------- // Enums. // /** * An {@code EventThread} has seven distinct states: *
    *
  1. * {@code NOT_STARTED}: The {@code EventThread} was * instantiated but not yet started. *
  2. *
  3. * {@code STARTING}: An {@code EventThread} is * instantiated and {@code start()} is executing * but not yet completed. *
  4. *
  5. * {@code RUNNING}: An {@code EventThread} instance was * started but {@link #halt} or {@link #haltNow} have not * yet been called. *
  6. *
  7. * {@code HALT_DRAIN}: {@link #halt} was called and the * the {@code EventThread} is processing existing * enqueued events. No new events may be added. *
  8. *
  9. * {@code HALT_NOW}: {@link #haltNow} was called; all * enqueued events are ignored. *
  10. *
  11. * {@code STOPPING}: An {@code EventThread} has exited * the event processing thread and called the * {@code stopping} method. *
  12. *
  13. * {@code HALTED}: An {@code EventThread} has exited * its {@code run} method. *
  14. *
*/ public enum RunState { /** * The event thread is not yet started. */ NOT_STARTED, /** * The event thread is calling the subclass starting() * method. */ STARTING, /** * The thread is processing events. */ RUNNING, /** * The thread is waiting for the queue to drain * before halting. */ HALT_DRAIN, /** * The thread is stopping now. */ HALT_NOW, /** * The event thread has stopped processing the event * queue and has passed control to the subclass * {@code stopping} method. */ STOPPING, /** * The event thread has completed its clean up * performed by the {@code stopping} method and is * no longer running. This thread may not be * restarted. */ HALTED } // end of enum RunState //--------------------------------------------------------------- // Inner Classes. // /** * Place this event on to the queue when then thread is put * into the HALT_DRAIN state. When the thread procedure sees * this event, it goes into the HALT_NOW state. */ private static final class HaltEvent { //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new HaltEvent instance. */ private HaltEvent() {} // // end of Constructors. //------------------------------------------------------- //----------------------------------------------------------- // Member data. // } // end of class HaltEvent } // end of class EventThread // // CHANGE LOG // $Log: EventThread.java,v $ // Revision 1.18 2007/02/23 13:38:38 charlesr // Corrected javadoc comments. // // Revision 1.17 2006/10/01 18:03:15 charlesr // Removed set start and stop signals to null. // // Revision 1.16 2006/10/01 17:32:57 charlesr // Added waitFlag parameter to halt(), haltNow() methods // and added start(boolean waitFlag) method. If waitFlag // is true, then these methods wait for the thread to // either stop or start, respectively. // // Revision 1.15 2006/04/28 16:14:04 charlesr // *** empty log message *** // // Revision 1.14 2006/01/03 18:00:10 charlesr // Modified halt() so that when it catches an // InterruptedException, it sets the run state to HALT_NOW // and then interrupts the thread again. // // Revision 1.13 2005/12/30 02:34:55 charlesr // Javadoc corrections. // // Revision 1.12 2005/12/23 14:34:17 charlesr // Improve thread halting to be thread-safe. // // Revision 1.11 2005/12/20 20:09:12 charlesr // Removed interrupt call from halt method. // // Revision 1.10 2005/12/20 15:12:47 charlesr // Check event instanceof HaltEvent only if the run state // is HALT_DRAINING. // // Revision 1.9 2005/12/20 01:24:26 charlesr // Added HaltEvent inner class to note when an event thread // has completed draining. // // Revision 1.8 2005/07/20 23:58:53 charlesr // Moved to Java 5: // + Using java.util.concurrent.BlockingQueue for events. // + Dropped handleQueue(). Only handleEvent is available. // // Revision 1.7 2005/03/07 18:12:15 charlesr // Create queueCopy list once and reuse. // // Revision 1.6 2004/12/26 13:37:12 charlesr // Replaced notifyAll() with notify(). // // Revision 1.5 2004/12/14 19:16:26 charlesr // Updated ThreadListener interface. // // Revision 1.4 2004/09/08 14:40:31 charlesr // Removed extraneous if condition. // Using java.util.logging.Logger method to output exceptions. // // Revision 1.3 2004/08/15 21:16:19 charlesr // Added STARTING run state and set _runState in constructors. // // Revision 1.2 2004/05/24 15:31:32 charlesr // Added public boolean isHalted() method. // // Revision 1.1 2004/04/23 00:25:35 charlesr // Generate default thread names to distinguish event threads from // other Java threads. // // Revision 1.0 2003/11/20 01:45:58 charlesr // Initial revision //