Class SequentialTask

  • All Implemented Interfaces:
    Runnable

    public final class SequentialTask
    extends Object
    implements Runnable
    When using a thread pool to execute tasks there is no guarantee the tasks will be executed in the same sequential order as offered to the pool. If the tasks are independent of each other, then order is unimportant. But if the tasks are chronologically dependent and must be exected in order, then the lack of guaranteed order is problematic.

    SequentialTask offers a solution between a dedicated event thread and a thread pool. Tasks associated with the same object are forced to execute in sequential order. Each task is assigned an index based on its associated object and executes only when its index becomes the current index. Otherwise, the task waits for its index to be reached. When the task completes, the current index is incremented and all waiting sequential tasks are notified of the new index.

    By associating the task index with an object, it is possible for multiple tasks associated with different objects to execute simultaneously. Only tasks associated with the same object are forced to execute serially. A weak hash map is used to associate an object with its task index values. This allows objects passed to createTask to be garbage collected and automatically removed from the task object index map.

    Example Use: The Observer Pattern

    In the Observer Pattern (GoF pp. 293 - 299) the Subject class' Notify() method calls back observers:
       
     public void Notify()
     {
         for (Observers o: observers)
         {
             o.Update();
         }
     }
       
     
    This callback can be converted from updating each observer in turn to a multi-threaded approach using a thread pool to execute each callback:
       
     public void Notify()
     {
         Iterator<Observer> oit;
    
         for (oit = observers.iterator(); oit.hasNext();)
         {
             // The run task requires that the Observer local
             // variable be final. Hence using an iterator and
             // declaring the variable within the for body.
             final Observer o = oit.next();
    
             threadPool.execute(
                 new Runnable()
                 {
                     @Override
                     public void run()
                     {
                         o.Update();
                     }
                 });
         }
     }
       
     
    The above code is acceptable if callback ordering is not a factor. Consider the following code;
       
     // s0 and s1 are both Subject instances. Assume that their
     // respective observer lists have some observer instances in
     // common (that is, a non-null intersection of observers).
     s0.Notify();
     s1.Notify()
       
     
    If it does not matter if s1 updates an observer before s0's update for the same observer, then using the above thread pool solution is acceptable. But if s0's update must occur before s1's update, then the threads must be serialized. SequentialTask provides this capability:
       
     public void Notify()
     {
         Iterator<Observer> oit;
    
         for (oit = observers.iterator(); oit.hasNext();)
         {
             // The run task requires that the Observer local
             // variable be final. Hence using an iterator and
             // declaring the variable within the for body.
             final Observer o = oit.next();
    
             threadPool.execute(
                 SequentialTask.createTask(
                     o,
                     new Runnable()
                     {
                         @Override
                         public void run()
                         {
                             o.Update();
                         }
                     }
                 });
         }
     }
       
     
    s0's call to o.Update() is guaranteed to occur before s1's o.Update(). However calls to Update() for different observers can execute in parallel. Only updates to the same observer are serialized.
    Author:
    Charles Rapp
    • Method Detail

      • run

        public void run()
        Synchronizes on the task index waiting for its turn to run the user task. Once the index is reached, the user task is executed. Once execution completes, the task index is incremented and waiting sequential tasks are notified concerning the new current index.

        If the user task throws an exception, it is caught and reported via the supplied logger at the specified level.

        Specified by:
        run in interface Runnable
      • index

        public int index()
        Returns the task's run index.
        Returns:
        the task's run index.
      • createTask

        public static SequentialTask createTask​(Object o,
                                                Runnable task)
        Creates a sequential task for the given object and task. If task throws an exception, it will be caught and quietly ignored.
        Parameters:
        o - The task is associated with this object.
        task - Runs this user task.
        Returns:
        A sequential task associated with o and executes task.
        Throws:
        IllegalArgumentException - if either o or task is null.
      • createTask

        public static SequentialTask createTask​(Object o,
                                                Runnable task,
                                                Logger logger,
                                                Level logLevel)
        Creates a sequential task for the given object and task. If task throws an exception, it will be logged to logger at logLevel.
        Parameters:
        o - The task is associated with this object.
        task - Runs this user task.
        logger - If task throws an exception, then report it via this logger.
        logLevel - Report task exception at this log level.
        Returns:
        A sequential task associated with o and executes task.
        Throws:
        IllegalArgumentException - if either o or task is null.