
©1996 Image Club Graphics A division of Adobe Systems Incorporated
Java EE provides a the
TimerService to allow you to schedule actions to run at a future time. The ability to schedule timed jobs, or just delay a piece of work so it runs asynchronously, is an important part of most software systems. When I first read about the
TimerService I was a bit confused by the way that it works and initially I avoided uses it because I didn’t understand it. However, now that I have a clearer idea of how to use it, I’m quite pleased with it.
First of all, the features of the TimerService:
- Each bean gets its own
TimerService and its own list of timers
- Timers are stored in the application container’s internal database and survive shutdown, restart, and redeployment of the application
- When timers are fired, they usually create an EJB Transaction so you can access the database (Entity beans)
How do I use it?
The timer service is injected using annotations, like the EntityManager and EJBs:
@Resource
TimerService timerService;
To schedule a future event, use the createTimer() method, for example:
public void initTimers() {
timerService.createTimer(5*1000, null); // Runs ~ 5 seconds from now
}
Looking at that code, you’ll see that we’ve hit the first piece of “weirdness” with TimerService - you don’t specify what to do when the timer fires! Luckily, the Java EE Tutorial is kind enough to show us this much information; by annotating a method with @Timeout we indicate that this method should be called when any timer for that bean’s injected TimerService fires. In point form:
- The method annotation
@Timeout is invoked when the timer fires
- The same method is used for all timers on the bean
- The bean whose timeout method to call is determined based on which bean the
TimerService was injected into
Example:
@Timeout
public void timerFired(Timer timer) {
System.out.println("Timer fired!");
}
This is basically where the Java EE Tutorial leaves you - I had more questions.
What if I have multiple timers?
Many beans will want to have multiple timers running at once, obviously. The TimerService supports this - and quite well, in fact - using the info field of the timer. When you call createTimer() you pass in the info value as the last parameter. This is a Serializable object which is stored to the database along with the timer and you can retrieve it in your timeout method using the getInfo() method.
Here’s one example where we have different types of events we want to handle, but the events don’t contain any custom data:
enum TimedEvent {
EVERY_10_SECONDS(10*1000, 0),
HOURLY(3600*1000, 0),
DAILY(24*3600*1000, 4*3600*1000), // Add a few hours to avoid DST issues
WEEKLY(7*24*3600*1000, 4*3600*1000),
EVERY_30_DAYS(30*24*3600*1000, 4*3600*1000),
MONTHLY(1000,0);
final long interval;
final long offset;
private TimedEvent(long interval, long offset) { this.interval = interval; this.offset = offset;}
public long getInterval() { return interval; }
public long getOffset() { return offset; }
}
public void scheduleTimers() {
long now = System.currentTimeMillis();
for(TimedEvent te: TimedEvent.values()) {
if(te == TimedEvent.MONTHLY) {
Calendar cal = Calendar.getInstance();
cal.clear(Calendar.MINUTE); cal.clear(Calendar.SECOND);
cal.set(Calendar.HOUR, 4);
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.add(Calendar.MONTH, 1);
timerService.schedule(cal.getTime(), te);
} else {
timerService.schedule(now - (now % te.interval) + offset, te.interval, te);
}
}
}
@Timeout
public void timerFired(Timer timer) {
TimedEvent evt = (TimedEvent)timer.getInfo();
switch(evt) {
case EVERY_10_SECONDS: // do stuff we do every 10 seconds
case HOURLY: // do stuff we do every hour
case DAILY: // do stuff we do every day at midnight
case WEEKLY: // do stuff we do every week
case EVERY_30_DAYS: // do stuff we do every 30 days
case MONTHLY: // Have to manually schedule the next one, months are not fixed length
Calendar cal = Calendar.getInstance();
cal.clear(Calendar.MINUTE); cal.clear(Calendar.SECOND);
cal.set(Calendar.HOUR, 4);
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.add(Calendar.MONTH, 1);
timerService.schedule(cal.getTime(), evt);
// do monthly stuff ...
break;
}
}
Remember that due to daylight savings time, you should avoid scheduling things at midnight in your current timezone, because they may end up being at 11pm the previous day when the time changes which might give unexpected results.
How do I start the timers?
One important element missing from the above is: who is going to call scheduleTimers()?
This does seem to be a bit of a gap in the Java EE spec; they don’t provide an obvious way to run a method when your EJB Jar is loaded (I haven’t found one).
I’ve seen examples that use the @PostConstruct annotation on a method, like this:
@PostConstruct
public void scheduleTimers() {
// ...
}
This would cause scheduleTimers() to be called each time an instance of your session bean is created before the first interface method is called on it. I haven’t tried this method because I only recently encountered it, and although it seems promising there are a couple of things I don’t know about it:
- Would it be called several times on a stateless session bean if the container decided to create multiple instances of that bean?
- For a stateless session bean, will it be called if no methods are ever called?
The method I’ve been using with some success is to create a ServletContextListener which invokes the scheduleTimers() method, like this:
public class MyBeanContextListener implements ServletContextListener {
Logger logger = LoggerFactory.getLogger(getClass());
@EJB
MyBean bean;
@Override
public void contextDestroyed(ServletContextEvent event) {
// TODO Should we cancel timers here?
}
@Override
public void contextInitialized(ServletContextEvent event) {
try {
bean.scheduleTimers()
} catch(Exception x) {
logger.error("Failed to schedule timers", x);
}
}
This has to be registered in the web.xml for a WAR in the same EAR, like this:
<listener>
<listener-class>MyBeanContextListener</listener-class>
</listener>
It will be run when the WAR file is loaded or if the application server is restarted. However, this does mean that if you restart your application server, you may end up scheduling this method twice! Which brings us to…
How do avoid scheduleing the same timer twice?
I was previously burned because over time my timers had been scheduled multiple times, so I had the same code running twice. This would have been OK, except that code wasn’t using locking correctly. Oops!
Anyway, now you can learn from my mistakes. Before scheduling new timers, you should check for and cancel any old timers that are no longer relevant. For example:
public void scheduleTimers() {
for(Object timerObj : timerService.getTimers()) { // No clue why getTimers() doesn't have at parameterized collection
Timer timer = (Timer)timerObj;
if(timer.getInfo() instanceof TimedEvent) // Cancel all our recurring events
timer.cancel();
}
// Now (re-)schedule the timers again
// As an alternative, you could detect the timers that are already scheduled and not re-schedule them.
}
What sorts of cool things can I put into the info field of a Timer?
When I first used the TimerService I used a String for the info field of the timer, like in the example in the Java EE Tutorial. The example never even used that string, which made the example a bit puzzling - I initially thought the string was there to identify the timer elsewhere, like in the monitoring system. Not so! A String is about the least useful thing you can use as the info field of your Timer - you can put any Serializable object there.
In one of my session beans I created an interface called Job with a single method perform(MyBean bean). By creating Serializable objects that implement this interface and call back to the bean with the appropriate method, I could schedule various different operations at different times, including some parameters to those objects, which are stored as members of the Job implementation.
You could also put an Entity Bean into the info field if it is Serializable, although if it has any lazy collections or non-transient references to non-serializable objects it wouldn’t work. I’d suggest putting the primary key of your entity instead and look it up when the timer fires. This will probably have fewer issues to deal with.
Will a timer survive redeployment of my enterprise application?
It’s pretty clear from the documentation that you can shut down the application server and start it up again and your timers will still fire. It will also notice any timers that should have fired when the application server was not running and fire them on startup. Also, clearly any timers you cancel will not fire again.
However, the timers for an EAR will be cancelled if the EAR is undeployed or redeployed. For this reason you should always be prepared to reinstate timers that could have been cancelled by a deployment operation, and not duplicate timers which may still be around because the application server has merely been restarted. This also means you can’t assume your timer will necessarily fire, since your application could be undeployed or redeployed before the timer could be fired. You will need an algorithm to reinstate or reinforce a timer’s work in case it is cancelled by the container.
What are some good uses for timers?
- Sending emails It can be useful to send an email in a separate transaction/thread than the one that causes it to be sent. If email sending fails, you can just re-schedule the email to try again later, which can make your email delivery more reliable.
- Asynchronous one-way operations - some operations on a bean aren’t supposed to keep the caller waiting, or even interfere with their transaction by throwing an exception and triggering an unwanted rollback. By scheduling a timer with a zero timeout I can defer that work. Examples: sending email (as above), cleaning up temporary files, …
- Break down long-running operations - if you have a long-running operation iterating over many objects, which doesn’t necessarily have to run everything in one transaction, you can have an object storing the “working state” of that operation and have it run one chunk, then schedule itself to run the next chunk, and so on until it is complete. This avoids a heavy memory footprint that could be caused by loading all those objects into the session. In theory you can also use
EntityManager.clear() to reduce the size of your session but in practice I’ve found this method may trigger various bits of poorly-tested code, lazy initialization exceptions, and more.
