Synchronized Singleton Pattern

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");
  }
 }

Use Cases

  • Singleton - A singleton with public synchronized run() that will perform Process.
  • Process - The process the singleton will perform. The amount of times this process can be run simultaniously is limited, it most likely something that is CPU intensive or locks$

resource.

  • CALL_FREQUENCY - The frequency at which singleton.run() is called.
  • PROCESS_LENGTH - The amount of time it takes process to finish. PROCESS_LENGTH is longer than CALL_FREQUENCY.
  • User A human user or automated process that may call singleton.run().

UseCase 1 - No change in data.

Usecase:

  1. A user calls singleton.run().
  2. A user calls singleton.run() 1 or more times before the first call has finished. There was no change in the data Process operates on.

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.

UseCase 2 - Change in data

  1. A user calls singleton.run().
  2. A user modifies data.
  3. A user calls singleton.run() but there is already a running WorkerThread
  4. A user repeats steps 2 and 3 an zero or more times before the WorkerThread from the initial call to singleton.run() completes.

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.



Why a Queue of Length 1?

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.

Monitoring the Process.

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!

Sample Code & Testing SynchronizedSingleton

$ 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");
        }
    }
}
 
development/deprecated/synchronized_singleton_pattern.txt · Last modified: 2011/04/25 23:06 by ramereth
 
Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki