Valhalla Legends Forums Archive | Java Programming | Timed Event Queue

AuthorMessageTime
Tuberload
This utility allows you to schedule events to be executed in a timed fashion. The utility runs on a background thread. It works by passing a reference to the TimedEvent that will be notified of the event, the timestamp for when the event is to be dispatched, and the event id that is used to determine the nature of the event, to the queue. The queue will then notify the recipient of the event when the time comes.

The utility currently has problems handling multiple thread input. If used by one thread it works fine, but when multiple threads start scheduling events after awhile it will stop executing events all together. If anyone can point me to my problem, it would be greatly appreciated. As soon as I figure it out, I will post an update. I am releasing it right now, because as previously stated it works well in a single thread environment.

To answer a question right off the bat, yes I am aware of the Timer/TimerTask objects already present in Java. I made this utility for the sake of learning.

Timed Event Queue home

[code]/**
*   Stores and generates timed events.
*   <p>
*   The queue size will dynamically grow if the need arises, otherwise it will
*   just clean itself of previously executed events.
*   <p>
*   Real time accuracy is <b>not</b> guaranteed. Works well in a single threaded
*   environment. Problems start occuring when multiple threads start scheduling
*   events.
*
*   @author Tuberload
*/

public class TimedEventQueue implements Runnable
{
   private static TimedEventQueue instance;
   private static Thread t;
   private boolean suspended = false;
   // event queue variables
   private TimedEvent[] callerQ;            // references to the recipient
                                    // of the event
   private long[] timestampQ;               // the timestamp for the event
   private int[] event_idQ;               // event id returned to caller
   private int put_loc;
   private int get_loc;
   private static final int INIT_SIZE = 25;   // initialize size of the queue
   
   /**
    *   Constructor is protected to prevent multiple instances from being
    * created.
    */
   protected TimedEventQueue()
   {
      // initialize the queue
      callerQ = new TimedEvent[INIT_SIZE];
      timestampQ = new long[INIT_SIZE];
      event_idQ = new int[INIT_SIZE];
      put_loc = get_loc = 0;
      
      // initialize the thread
      t = new Thread (this, "Timed Event Queue");
      suspended = false;
      t.start();
   }
   
   /**
    *   Returns a reference to the queue.
    */
   public synchronized static TimedEventQueue getInstance()
   {
      if (instance == null)
      {
         instance = new TimedEventQueue();
      }
      return instance;
   }
   
   /**
    *   Put an item into the queue.
    * <p>
    *   If the end of the queue is reached it will first attempt to make room
    * for more events by removing previously executed events. If the queue is
    *   full of unexecuted events it will increment its size. The idea is to
    *   keep the performance costs down by minimizing the time spent maintaining
    *   the queue size, and also keep memory consumption low.
    */
   public synchronized void schedule (TimedEvent caller, long timestamp,
      int event_id)
   {
      try
      {
         // check for end of queue
         if (put_loc >= callerQ.length)
         {
            // check if there is any executed events that can be removed
            int delete = executedEvents();
            if (delete == 0)   // increment the size of the queue
            {
               int length = callerQ.length;
               
               TimedEvent[] tmp1 = new TimedEvent[length];
               long[] tmp2 = new long[length];
               int[] tmp3 = new int[length];
               
               System.arraycopy (callerQ, 0, tmp1, 0, length);
               System.arraycopy (timestampQ, 0, tmp2, 0, length);
               System.arraycopy (event_idQ, 0, tmp3, 0, length);
               
               callerQ = new TimedEvent[length*2];
               timestampQ = new long[length*2];
               event_idQ = new int[length*2];
               
               System.arraycopy (tmp1, 0, callerQ, 0, length);
               System.arraycopy (tmp2, 0, timestampQ, 0, length);
               System.arraycopy (tmp3, 0, event_idQ, 0, length);
            }
            else   // remove previously executed events
            {
               // new length of the array will be just the unexecuted events
               int length = callerQ.length-delete;
               // use this when previous queue length required
               int init_length = callerQ.length;
               TimedEvent[] tmp1 = new TimedEvent[length];
               long[] tmp2 = new long[length];
               int[] tmp3 = new int[length];
               // copy all unexecuted events to a temporary array
               System.arraycopy (callerQ, delete-1, tmp1, 0, length);
               System.arraycopy (timestampQ, delete-1, tmp2, 0, length);
               System.arraycopy (event_idQ, delete-1, tmp3, 0, length);
               // re-initialize the queues
               callerQ = new TimedEvent[init_length];
               timestampQ = new long[init_length];
               event_idQ = new int[init_length];
               // copy unexecuted events back into the queue
               System.arraycopy (tmp1, 0, callerQ, 0, length);
               System.arraycopy (tmp2, 0, timestampQ, 0, length);
               System.arraycopy (tmp3, 0, event_idQ, 0, length);
               // set the put/get locations to the correct position
               if (put_loc - delete >= 0)
                  put_loc -= delete;
               if (get_loc - delete >= 0)
                  get_loc -= delete;
            }

         }
      
         callerQ[put_loc] = caller;
         timestampQ[put_loc] = timestamp;
         event_idQ[put_loc] = event_id;
         put_loc++;
      } catch (Exception exc)
      {
         System.out.println (exc);
         exc.printStackTrace();
      }
   }
   
   /**
    *   Get the number of executed events that can be deleted.
    *
    * @return 0 will be returned if there is no executed events
    *   @return A number greater than zero will be returned if there is events
    *         to delete.
    */
   private synchronized int executedEvents()
   {
      int executed = 0;
      long time = System.currentTimeMillis();
      for (int i = 0; i < timestampQ.length; i++)
      {
         if (timestampQ[i] >= time)
            break;   // end of available queue spots to delete
         else
            executed++;   // previously executed event found
      }
      
      return executed;
   }
   
   /**
    *   Get the next item in the queue.
    */
   private synchronized TimedEventInfo get()
   {
      TimedEventInfo info = new TimedEventInfo (callerQ[get_loc],
         timestampQ[get_loc], event_idQ[get_loc]);
      
      if (get_loc != callerQ.length)
         get_loc++;
      
      return info;
   }
   
   /**
    *   See what the next item in the queue is.
    */
   public synchronized TimedEventInfo peek()
   {
      TimedEventInfo info;
      
      if (get_loc == callerQ.length)
      {
         info = null;
      }
      else
      {
         info = new TimedEventInfo (callerQ[get_loc],
            timestampQ[get_loc], event_idQ[get_loc]);
      }
         
      return info;
   }
   
   /**
    *   Entry point of the thread.
    */
   public void run()
   {
      long cur_time;
      TimedEventInfo info;
      TimedEvent event;
      boolean loop = false;
      try
      {
         // loop acts as the Timed of the thread
         while (true)
         {
            cur_time = System.currentTimeMillis();
            info = peek();
            
            //if (info == null) System.out.println ("Null peek()");
            
            // event to be executed
            if (info != null)
            {
               if (cur_time >= info.getTimestamp())
               {
                  info = get();
                  event = info.getCaller();
                  event.timedEvent (info.getEventID());
               }
            }
            Thread.sleep (1);   // keep cpu usage down
            
            // suspend execution of the thread if it is paused
            while (suspended)
            {
               Thread.sleep (1);
            }
         }
      } catch (NullPointerException exc)
      {
         // temporary solution
      }
      catch (Exception exc)
      {
         System.out.println (exc);
         exc.printStackTrace();
      }
   }
   
   /**
    *   Pauses execution of the TimedEventQueue.
    */
   public synchronized void pause()
   {
      suspended = true;
   }
   
   /**
    *   Resumes execution of the TimedEventQueue.
    */
   public synchronized void resume()
   {
      suspended = false;
   }
}[/code]

The following interface must be implemented by any object wishing to recieve events from the queue.

[code]/**
*   Interface must be implemented by any class wishing to recieved timed events.
*
*   @author Tuberload
*/

public interface TimedEvent
{
   public void timedEvent (int event_id);
}[/code]

The following class is used to store information about the timed event.

[code]/**
*   Used to pass information about the timed event.
*
*   @author Tuberload
*/

public class TimedEventInfo
{
   private TimedEvent caller;
   private long timestamp;
   private int event_id;
   /**
    *   Store the information.
    */
   public TimedEventInfo (TimedEvent caller, long timestamp, int event_id)
   {
      this.caller = caller;
      this.timestamp = timestamp;
      this.event_id = event_id;
   }
   /**
    *   Returns a reference to the recipient of the event. This allows objects
    *   to create timed events for other objects that inherit the TimedEvent
    *   interface.
    */
   public TimedEvent getCaller()
   {
      return caller;
   }
   /**
    *   The timestamp is used to determine when the event should be triggered.
    */
   public long getTimestamp()
   {
      return timestamp;
   }
   /**
    *   The event id is sent to the recipient of the event. It is the current
    *   method used for determining what kind of event is being recieved. The
    *   recipient of the event is responsible for knowing what to do based on
    *   the recieved event id.
    */
   public int getEventID()
   {
      return event_id;
   }
}[/code]

Last but not least, here is an example demonstrating how to use the timed event queue. All it does is create another timed event every time an event occurs.

[code]public class TEQTest implements TimedEvent
{
   private TimedEventQueue teq;
   private int id;
   
   public static void main (String[] args)
   {
      TEQTest test1 = new TEQTest(1);
   }
   
   
   public TEQTest (int id)
   {
      this.id = id;
      teq = TimedEventQueue.getInstance();
      long time = System.currentTimeMillis();
      
      teq.schedule (this, time+1000, 1);
      
      while (true)
      {
         try
         {
            Thread.sleep (1);
         } catch (Exception exc) {}
      }
   }
   
   public synchronized void timedEvent (int event_id)
   {
      long time = System.currentTimeMillis();
      
      teq.schedule (this, time+1000*id, event_id+1);
      System.out.println ("Event Executed(" + id + "): " + event_id);
   }
}[/code]
June 7, 2004, 11:40 PM
DaRk-FeAnOr
I wrote a "timer class" which is basically the same idea:

[code]
// Feanor[xL]
// 5/27/04
// A Timer class to time Timeable objects

import java.util.*;

public class Timer extends Thread{
   private static Timer instance;
   private static Vector alarms = new Vector();

   private Timer(long secs, Timeable time){
      instance = this;
      alarms.add(new Alarm(secs, time));
      start();
   }
   
   public static void setAlarm(long secs, Timeable time){
      if(instance == null){
         instance = new Timer(secs, time);
      }
      else{
         alarms.add(new Alarm(secs, time));
      }
   }
   
   public void run(){
      while(alarms.size() > 0){
         try{
            sleep(1000);
         }
         catch(InterruptedException e){
            System.out.println(e);
         }
         for(int pos = 0; pos < alarms.size(); pos++){
            Alarm ticking = (Alarm)alarms.get(pos);
            boolean stillon = ticking.tick();
            if(stillon == false){
               alarms.remove(ticking);
               if(alarms.size() == 0){
                  instance = null;
               }
            }   
      
         }
      }
   }   
      
}

class Alarm {
   private long secs;
   private Timeable time;
   
   public Alarm(long insecs, Timeable intime){
      secs = insecs;
      time = intime;
   }
   
   public String toString(){
      return secs + " " + time;
   }
   
   public boolean tick(){
      boolean toreturn;
      if(secs == 0){
         wakeUp();
         toreturn = false;
      }
      else{
         secs--;
         toreturn = true;
      }
      return toreturn;
   }
   
   private void wakeUp(){
      time.timedout();
   }
}
[/code]
[code]
public interface Timeable{
   public void timedout();
}
[/code]
July 12, 2004, 6:07 PM
Tuberload
Yes basically the same thing, except I handle the storage of the data and you use a Vector. I need to redesign my collection code and move it to a separate class though. I made this when I was first learning this sort of thing. Laziness has been a factor.

Plus mine allows for event id's to be associated with the timed event so you can do different things with the same object.
July 12, 2004, 7:07 PM
St0rm.iD
plz don't make another class called Timer; there's two of them in the standard library already.
July 12, 2004, 10:08 PM
DaRk-FeAnOr
"Timer" is a killer class name and no Sun documentation can take that away from me. :P
July 13, 2004, 3:27 PM

Search