net.sf.eBus.util.EventThread Maven / Gradle / Ivy
//
// 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:
*
* -
* 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.
*
* -
* 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.
*
* -
* 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.
*
*
* 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