Using the Timer Service

In this section, we will look at two examples of possible applications of the timer service. We will look at enough of the source code in this chapter to discuss the fundamental concepts, but you will get the most benefit from this chapter by having the full source listings available. In the first example, we will use the timer service as a scheduler to start the sample application via a timer notification that is emitted once. The second example shows how to use the timer service to handle repeated timer notifications to write log messages to disk.

As we’ve seen, the minimal steps to use the timer service are:

  1. Create the timer service by instantiating the Timer class.

  2. Add a notification, which includes at a minimum the notification type (which is up to you to define) and the date the notification is to start.

  3. Add a notification listener to the timer service.

  4. Start the timer.

Step 2 is repeated for each notification type that is to be sent by the timer service. Step 3 is repeated for as many notification listeners as are needed in the system. In the sample application code for this chapter, there are two notification types:

sample.timer.startController

The notification type string that indicates the controller is to be started

sample.timer.flushlog

The notification type string that indicates that any queued messages are to be written (i.e., committed) to the log

There are also two notification listeners:

sample.timer.Scheduler

The class that is responsible for creating the timer service and adding the above notifications

sample.utility.MessageLogQueue

The class that is used to queue log messages, listen for sample.timer.flushlog notifications, and write them to the disk-based log file

The Scheduler class acts as the JMX agent for our example. Example 10-2 shows a partial source listing for this class.

Example 10-2. Partial source listing of the Scheduler class

package sample.timer;
import sample.utility.*;
// . . .
public class Scheduler {
  public static void main(String[] args) {
    try {
      Properties props = new Properties(  );
      FileInputStream propFile = new FileInputStream("scheduler.properties");
      props.load(propFile);
      String controllerStartWaitTime = (String)props.get("controller.startWaitTime");
      long startTime = System.currentTimeMillis(  ) +
        (new Long(controllerStartWaitTime)).longValue(  );
      Date startDate = new Date(startTime);
      Timer timer = new Timer(  );
      // Add notification that starts the controller. . .
      timer.addNotification("sample.timer.controllerStart",
                            null, 
                            props,
                            startDate);
      Listener listener = new Listener(  );
      timer.addNotificationListener(listener, null, null);
      // add notification that flushes the log. . .
      String logFlushWaitTime = (String)props.get("logger.flushWaitTime");
      timer.addNotification("sample.timer.flushlog",
                            "Time to flush the log to disk.",
                            null,
                            new Date(  ),
                            (new Long(logFlushWaitTime)).longValue(  ));
      timer.addNotificationListener(MessageLogQueue.instance(  ),
                                    null,
                                    null);
      timer.start(  );
      ObjectName objName = new ObjectName("Timer:type=generic");
      MBeanServer mbs = MBeanServerFactory.createMBeanServer(  );
      mbs.registerMBean(timer, objName);
    } catch (Exception e) {
    }
  }
}

The main( ) method of Scheduler contains the agent code for our example and reads the following properties from the file scheduler.properties:

Controller start wait time

The number of milliseconds to delay before starting the controller, used to calculate the start date for sending the sample.timer.startController notification

Log queue flush wait time

The number of milliseconds to wait before sending the sample.timer.flushlog notifications, which results in the listener writing any queued log messages to disk

Consumer work factor

The number of prime numbers calculated by the consumer thread for each item it removes from the queue

Supplier work factor

The number of prime numbers calculated by the supplier thread for each item it adds to the queue

Example 10-3 shows the contents of this file.

Example 10-3. The scheduler.properties file

controller.startWaitTime=60000
logger.flushWaitTime=10000
controller.supplier.workFactor=150
controller.consumer.workFactor=100

Using the property values in this file, the controller will start in approximately one minute and the queued log messages will be written to disk every 10 seconds.

Once the start wait time for the controller is read from the properties file, that wait time is added to the current system time to calculate the date (in milliseconds) when the notification to start the controller will be sent. Next, the timer service is created and the controller start notification is added. Notice that the Properties object we created earlier is passed as the userData parameter to the addNotification( ) call. This allows the listener access to the properties from scheduler.properties. Next, we create an instance of the Listener class, which will receive the notification to start the controller. We tell the timer service to send this notification to Listener by calling the timer’s addNotificationListener( ) method.

The second notification to be added is the one to flush any queued log messages to the log file on disk. Notice that we do not use a userData object in this case, and we indicate this by passing null on the addNotification( ) call. We indicate to the timer service that the notifications are to begin immediately by creating a new Date object, using Date’s default constructor. Finally, we indicate to the timer service that this notification is to be sent repeatedly, at an interval indicated by the value of the logger.flushWaitTime property. The MessageLogQueue class acts as a queue of log messages, which are not committed to the log file until it receives a notification to do so. It is implemented as a singleton[3] so that it can be shared by all other objects in the system. It is the MessageLogQueue that will act as the listener for this notification, and we obtain a reference to the singleton by calling its instance( ) method.

Once the notifications and listeners have been added, the timer service is started by calling its start( ) method. The timer service is an MBean, so we create an object name for it and register it with the MBean server. This allows the timer service to be monitored and controlled.

Handling the Controller Start Notification

When the controller start notification is sent, the Listener class handles the notification:

package sample.timer;
  
import javax.management.*;
  
public class Listener implements NotificationListener {
  public void handleNotification(Notification notification, Object obj) {
    String type = notification.getType(  );
    if (notification instanceof TimerNotification) {
      TimerNotification notif = (TimerNotification)notification;
      if (type.equals("sample.timer.controllerStart")) {
                       Properties props = (Properties)notif.getUserData(  );
        final String cwf = 
          (String)props.getProperty("controller.consumer.workFactor");
        final String swf = 
          (String)props.getProperty("controller.supplier.workFactor");
        Thread t = new Thread(new Runnable(  ) {
                         public void run(  ) {
                           Controller.main(new String[] {cwf, swf});
                         }
                       } );
                       t.start(  );
      }
    }
  }
}

The code for the Listener class is relatively simple, as it has to implement only the handleNotification( ) method of NotificationListener. Recall that when the timer service sends a notification, an instance of the TimerNotification class is emitted to all interested listeners. This is the only type of notification in which Listener is interested. If the notification is not of the right type (sample.timer.controllerStart), it is ignored. If the notification type string is of the correct type, the getUserData( ) method of the Notification class (TimerNotification’s parent class) is invoked to retrieve the Properties object we stored there when the controller start notification was added to the timer service. The Controller is started on a separate thread of execution, because Controller.main( ) does not terminate until all other threads in Controller (e.g., consumer and supplier threads) are finished. If we don’t start the Controller on its own thread from handleNotification( ), it may hang the timer service’s notification thread if we run the code on single-processor systems, and other notifications may not be sent by the timer service until Controller.main( ) is finished executing!

Handling the Message Queue Flush Notification

When the notification to flush queued messages to disk is sent by the timer service, the MessageLogQueue class handles it:

package sample.utility;
  
import javax.management.*;
public class MessageLogQueue extends MessageLog
  implements NotificationListener {
  // singleton stuff. . .
  private static MessageLogQueue _instance = null;
                 public static MessageLogQueue instance(  ) {
    if (_instance == null)
      _instance = new MessageLogQueue(  );
    return _instance;
  }
  
  private ArrayList _store = new ArrayList(10);
                 public synchronized void write(String message) {
    _store.add(message);
  }
  public synchronized void write(Throwable t) {
    _store.add(t);
  }
    
  public synchronized void handleNotification(Notification notification,
                                                             Object obj) {
                   if (notification instanceof TimerNotification) {
      String type = notification.getType(  );
      if (type.equals("sample.timer.flushlog")) {
        if (_store.size(  ) > 0) {
          Iterator iter = _store.iterator(  );
          while (iter.hasNext(  )) {
            Object message = iter.next(  );
            if (message instanceof String)
                             super.write((String)message);
                           else if (message instanceof Throwable)
                             super.write((Throwable)message);
          }
          _store.clear(  );
        }
      }
    }
  }
}

The emphasized lines in this code point out some of the features of the MessageLogQueue class. First, the class is implemented using the singleton pattern, so that there is only one instance of the class for the process (i.e., the JVM)—this allows all classes in the JVM to write their log messages to the log file in an asynchronous fashion. The backing store for the “queue” inside MessageLogQueue is an ArrayList, and access to the contents of the list must be synchronized.

As we saw earlier, if the Notification object that is sent is not a TimerNotification, it is simply ignored. This goes for the notification type as well, which must be sample.timer.flushlog. Any messages in the backing store for the queue are written out one at a time using an Iterator. Because both String and Throwable message types are allowed, the Java keyword instanceof is used to determine the type of the current entry in the list, so that it can be appropriately cast when delegating to the superclass’s write( ) method. Once the contents of the queue have been written to the log file, the backing store for the queue is emptied and is ready for the next batch of messages.



[3] See Gamma, et al. Design Patterns. Reading, MA: Addison Wesley, 1994.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset