March 14, 2010

Discrete Java Timer

Tags: ,

Java provides a number of methods to run code at scheduled future time, the implementations of ScheduledExecutorService generally being the best way at the moment. ScheduledExecutorService also defines methods to run the code a periodic intervals (scheduleAtFixedRate & scheduleWithFixedDelay). However, what if you have classes that need to be notified at regular discrete intervals and need to know which interval that are in. A timer is required that with a set periodicity fires an event on any listeners and passes the number of times the period has elapsed. What this is doing is breaking up continuous “realtime” into discrete blocks or quantums of time. For example, if the period is 10 seconds, at the start the time quantum is 0 until 10 seconds have elapsed and an event is fired denoting the start of the first time quantum. Ten seconds later another event fires as the 2nd time quantum starts, and so on.

This is a fairly simple bit of Java code. Any experienced Java developer has probably already worked out a good solution, but for me it has come up a couple of times in the last few months so I thought I’d write it down.

First, an interface for listeners to the discrete time blocks. Whenever a new time quantum starts this method will be called on any registered listeners.

public interface TimeQuantumListener {	
	void nextTimeQuantum(long timeQuantum);
}

Then the timer itself. It acts as detailed above. Once started the timer fires an event to it’s listeners with the period specified in the constructor, while keeping a count of how many such events have been fired.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

public class TimeQuantumTimer {

	private final List<TimeQuantumListener> listeners = new CopyOnWriteArrayList<TimeQuantumListener>();
	private final long period;
	private final TimeUnit timeUnit;
	private final ScheduledExecutorService scheduler;
	private final AtomicLong currentTimeQuantum = new AtomicLong(0);
	private final AtomicBoolean hasStarted = new AtomicBoolean(false);
	
	public TimeQuantumTimer(int period, TimeUnit timeUnit, ScheduledExecutorService scheduler) {
		this.period = period;
		this.timeUnit = timeUnit;
		this.scheduler = scheduler;
	}
	
	public TimeQuantumTimer(int period, TimeUnit timeUnit) {
		this(period, timeUnit, Executors.newSingleThreadScheduledExecutor());
	}
	
	public long getCurrentTime() {
		return currentTimeQuantum.get();
	}
	
	public void start() {
		if (!hasStarted.getAndSet(true)) {
			scheduler.scheduleAtFixedRate(new Runnable() {
				@Override public void run() {
					fireNextTimeQuantum(currentTimeQuantum.incrementAndGet());
				}			
			}, period, period, timeUnit);
		}
	}
	
	public void addListener(TimeQuantumListener listener) {
		listeners.add(listener);
	}
	
	private void fireNextTimeQuantum(long timeQuantum) {
		for (TimeQuantumListener l: listeners) {
			l.nextTimeQuantum(timeQuantum);
		}
	}
}

Here is a test for the timer. It uses DeterministicScheduler a very useful class from the jMock to mock out Java 1.5 scheduled executors.

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.TestCase;
import org.jmock.lib.concurrent.DeterministicScheduler;

public class TimeQuantumTimerTest extends TestCase {

	public void testNoStart() {
		DeterministicScheduler sched = new DeterministicScheduler();
		TimeQuantumTimer timer = new TimeQuantumTimer(1, TimeUnit.SECONDS, sched);
		assertEquals(0, timer.getCurrentTime());
		sched.tick(5, TimeUnit.SECONDS);
		assertEquals(0, timer.getCurrentTime());
	}
	
	public void testListeners() {
		DeterministicScheduler sched = new DeterministicScheduler();
		TimeQuantumTimer timer = new TimeQuantumTimer(1, TimeUnit.SECONDS, sched);
		final AtomicInteger count = new AtomicInteger(0);
		timer.addListener(new TimeQuantumListener() {	
			@Override public void nextTimeQuantum(long timeQuantum) {
				count.incrementAndGet();			
			}
		});	
		timer.start();
		assertEquals(0, count.get());
		sched.tick(5, TimeUnit.SECONDS);
		assertEquals(5, count.get());
		sched.tick(501, TimeUnit.MILLISECONDS);
		assertEquals(5, count.get());
		sched.tick(500, TimeUnit.MILLISECONDS);
		assertEquals(6, count.get());
	}
	
	public void testCurrentTime() {
		DeterministicScheduler sched = new DeterministicScheduler();
		TimeQuantumTimer timer = new TimeQuantumTimer(1, TimeUnit.SECONDS, sched);
		timer.start();
		assertEquals(0, timer.getCurrentTime());
		sched.tick(5, TimeUnit.SECONDS);
		assertEquals(5, timer.getCurrentTime());
		sched.tick(501, TimeUnit.MILLISECONDS);
		assertEquals(5, timer.getCurrentTime());
		sched.tick(500, TimeUnit.MILLISECONDS);
		assertEquals(6, timer.getCurrentTime());
	}
}

comments powered by Disqus