The Singleton Pattern is a programming pattern used to limit how many instances of a particular class can be created. It is used when a single instance of a component or pool of instances will be used by an entire application. Additionally to ensure a process is only run once concurrently a method within the singleton may be synchronized. This article describes and solves the problem of a synchronized method in a singleton that cannot complete its processing before it is called again. The problem with such a method is the method calls can queue. If the process takes a long time or is CPU intensive the resulting processing backlog could take hours or days to clear.
/***
* Your run of the mill thread safe singleton with a synchronized method run()
**/
public class VanillaSingleton {
private static VanillaSingleton instance = new VanillaSingleton();
public static VanillaSingleton getInstance() {return instance;}
private VanillaSingleton() {}
/***
* Simulate a lengthy process by sleeping
**/
public synchronized void run(){
System.out.println("I'm sleepy, taking a 10 second nap!");
try {
wait(10000);
} catch (InterruptedException e) {
System.out.println("Someone woke me up!");
}
System.out.println("I'm awake now");
}
}
resource.
Usecase:
subsequent calls to run() will queue. The data has not changed so the result of all calls to run() will result in the same end result, therefore the
subsequent calls waste cpu time and or unnecessarily lock objects. If PROCESS_LENGTH is measured in hours this could easily result in a queue that takes days to clear.
Solution:
Use threading to prevent endless queueing of calls to run() as well as allowing calls to run() to quickly return. Only a single thread will be
running at any given time.
The first call is working on stale data but the changes made for use case 1 will cause singleton.run() to return immediately. Process must be run again after WorkerThread finishes to process the data changed in step 2.
Create a queue of length 1. When the first request finishes it will start the waiting thread. Subsequently queued requests should be combined into a
single batch request.
The original problem this pattern attempted to solve was queueing. Queuing is needed in some cases but with batch processing it can be limited to the smallest possible size (1). Any number greater than 1 would be a mostly arbitrary choice. A queue big enough to hold all possible requests (infinite) just brings us back to the original problem.
VanillaSingleton.run() blocks until process is complete and then optionally returns some value. SynchronizedSingleton returns immediately, very likely before WorkerThread.process()
has completed. The calling thread may require a way to monitor the status of the WorkerThread in the case it must perform some sequential actions.
Observer Pattern
Using the Observer Pattern the calling thread can register itself or another object as an observer. The
WorkerThread would notify all observers when its state changes.
Status Object Singleton.run() can return a status object that will be updated by the WorkerThread associated with that call. The only requirement is that the object return$
not null. Its an object-by-reference so the original thread can monitor this object or store it for later use. Multiple requests that are merged into single queued request will share the
same status object. This method is used in the example below!status_sequence.png|align=center!
$ java -jar SynchronizedSingleton.jar 10 3
Simulating 3 calls to a process that takes 10 seconds to complete
Testing VanillaSingleton: 2006-07-11 11:55:24.995
Vanilla: taking a 10 second nap! Vanilla: I'm awake now Vanilla: taking a 10 second nap! Vanilla: I'm awake now Vanilla: taking a 10 second nap! Vanilla: I'm awake now
Finished at 2006-07-11 11:55:55.023 took 30028 milliseconds.
SynchronizedSingleton test 1, Multiple calls: 2006-07-11 11:55:55.026
Syncho: taking a 10 second nap! Synchro: I'm awake now Call 0 - ran Call 1 - did not run Call 2 - did not run
Finished at 2006-07-11 11:56:05.084 took 10058 milliseconds.
SynchronizedSingleton Test 2, Must Run: 2006-07-11 11:56:05.085
Syncho: taking a 10 second nap! Synchro: I'm awake now Syncho: taking a 10 second nap! Synchro: I'm awake now Call 0 - ran Call 1 - ran Call 2 - ran
Finished at 2006-07-11 11:56:05.084 took 20029 milliseconds.
Here is the example code for synchronized singleton:
/**
* Synchronized Singleton is an extension of the Singleton pattern to accomidate
* singletons that are used a single point for starting processes that are either
* CPU intensive or lock resources. Processes such as these would require that only
* one such process was running at any given time. Synchronization of methods would
* not be sufficient because of the possibility of method calls queing and backing
* up. This queuing would become more problematic as the duration of the method
* increases.
*
* An example of such a process is document indexing. Depending on the amount of
* documents and parameters used indexing documents can easily take hours or longer.
* This pattern will prevent things such as a user manually initiating a 2 hour long
* process multiple times, something that could potentially take days to finish.
*
* For a more in depth description of this pattern and its usecases see:
*
*
* @author Peter Krenesky
* Date: Jun 7, 2006
* Time: 9:43:07 AM
*/
public class SynchronizedSingleton {
private WorkerThread thread;
private WorkerThread next;
public int processTime = 3;
private static SynchronizedSingleton instance = new SynchronizedSingleton();
public static SynchronizedSingleton getInstance() {
return instance;
}
public SynchronizedSingleton() {}
/**
* Convenience method for run(boolean mustRun, boolean stopExisting). Implies run(false, false)
* @return Status object that will be updated as status changes
* @see #run(boolean mustRun)
*/
public Status run () {
return run(false);
}
/**
* Starts the processing done by this singleton. This method is synchronized
* to ensure only one process starts at a time.
* <p/>
* This method can potentially, and most likely, will return before the process completes.
* If you require an alert when the process finishes a listener pattern or similar pattern
* can be used.
*
* @param mustRun Indicates an urgent update that must be processed
* even if a process is already running
*
* @return Status object that will be updated as status changes
*/
public synchronized Status run (boolean mustRun) {
Status result = new Status();
if (thread == null ) {
/**
* Process is not running, safe to start
*/
try {
thread = new WorkerThread(this,result);
thread.start();
result.status = Status.STATUS_RUNNING;
} catch (Exception e) {
System.out.println("Exception creating worker thread");
//cleanup the thread on exception
thread = null;
}
} else if (mustRun) {
/**
* Process is running but this call must be started
* Queue the process to run.
*/
if (next == null) {
/**
* In this example we only a queue that only contains
* a single object. In the case that a call to run()
* contained parameters a multi object queue would work
* though it would be most efficient to combine all queued
* requests into a single request.
*/
next = new WorkerThread(this, result);
result.status = Status.STATUS_WAITING;
} else {
/**
* There is already a thread in the queue. Return
* the result object of that thread.
*/
result = next.result;
}
} else {
result.status = Status.STATUS_NOT_RAN;
}
return result;
}
/**
* WorkerThread is used to make processing asynchronous, allowing quick returns from
* calls to <b>run(boolean, boolean)</b>. Requests can continue to be received and handled
* by <b>run(boolean, boolean)</b> while a WorkerThread is running. WorkerThread makes a callback
* to <b>SynchronizedSingleton.process()</b> so that this inner class could be move to
* a separate file. For this example cleaner code is being sacrificed for a single package
*
* @see SynchronizedSingleton#run(boolean mustRun)
* @see #process()
*/
private class WorkerThread extends Thread {
SynchronizedSingleton parent = null;
Status result;
public WorkerThread(SynchronizedSingleton parent, Status result) {
this.parent = parent;
this.result = result;
}
public void run() {
try {
process();
} finally {
if (next != null) {
/**
* There is another thread waiting to start so update
* the parents reference and start it. This section
* of code must be synchronized on next to prevent
*/
synchronized (next) {
thread = next;
next = null;
thread.start();
thread.result.status = Status.STATUS_RUNNING;
}
} else {
/**
* Cleanup parents reference to this thread. This allows
* further requests to be accepted
*/
thread = null;
}
this.result.status = Status.STATUS_RAN;
}
}
/**
* For this example process just goes to sleep for 5 processTime to
* simulate a process taking a long time to return.
*/
protected void process() {
System.out.println(" Syncho: taking a " + processTime + " second nap!");
try {
sleep(processTime * 1000);
} catch (InterruptedException e) {
System.out.println(" Synchro: Someone woke me up!");
}
System.out.println(" Synchro: I'm awake now");
}
}
}