Accueil‎ > ‎

EJB - Scheduled Tasks with TimerService or ScheduledThreadPoolExecutor

posted 3 May 2019, 02:05 by Christophe Noël   [ updated 3 May 2019, 02:27 ]
EJB (3.1) provides a great support of Scheduled Tasks through the use of the Timer Service. That tool sounds great: you provide your schedule providing the interval, and an associated (serializable) object and EE container manages the execution of the scheduled method AND the persistence of timer and associated data.

Nevertheless, when you reach higher objectives, the Timer Service may reach its limits and you could face the following issues:
  • If you create an IntervalTimer (repeated task), you will notice that all triggered task are performed by a single thread. As long you have multiple schedule task executing for a long time, they are waiting in queue until the previous one is finished. For many reasons (e.g. task idle times) you would prefer running multiple threads.
  • A second issue with the IntervalTimer is, you cannot update/modify the associated data ! So you cannot actually save a state for the timer (for example the last action result).
  • Otherwise, if you create SingleActionTimer, perform the task, and recreate the SingleActionTimer (with the updated data), you will probably loose timers during server restart or application redeployment.
Don't make the mistake to call EJB asynchronous task or implement persistence OVER the Timer Service, you have reached the limit and the adapted solution is (nearly) peace of cake.

1. Thread Pool

Java SE 6 provides the right tool: the Scheduled Thread Pool Executor

You can define the corePoolSize (number of threads executing your tasks):
Executors.newScheduledThreadPool()

Then you submit your Runnable / Callable using the same parameters (initial delay, interval, then the TimeUnit being miliseconds, hours, days, etc.):
executor.scheduleAtFixedRate(task,  30000, interval,TimeUnit.MILLISECONDS);

Count the number of task is computed using the queue size and active tasks:
executor.getQueue().size()+ingestionExecutor.getActiveCount();

I also provide a few advices for the implementation:
  • A good idea to hold task related information in a Singleton, including the Executor
  • Use the @Lock(LockType.READ) annotation on the getExecutor() method
  • Do not call the scheduleAtFixedRate operation from the Singleton (if you want to save a few miliseconds). Instead, get the executor from a stateless bean, and schedule the task.
  • Define a concurrent hash map for storing all submitted task (i.e. ScheduledFuturew<?> instances)
  • Implement a Remove method which both remove the map entry and perform a Future.cancel()
  • To properly shut down an ExecutorService, include the method below (awaitTermination avoid interrupting active tasks which may corrupt the system):
  @PreDestroy
  void detroy() throws InterruptedException {
    // request immediate shutdown
  ingestionExecutor.shutdownNow();
  ingestionExecutor.awaitTermination(10, TimeUnit.SECONDS);
          }

2. Data Persistence

This post does not intend to teach how to create an Entity Manager and deal with entities. But note the following aspects would help depending on your use case:
  • Still in a performance purpose, the entities could be cached:
    • JPA: using the @Cacheable annotation (if your implementation supports it, such as eclipseLink). The default cache mode is <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode> (which requires the @Cacheable annotation) (see http://www.thejavageek.com/2014/05/29/jpa-caching/)
    • Manually: maintaining a concurrent hash map in memory (i.e; an Application Level cache)
  • In a very particular service, the persistence of a large set of entities needed to be delayed (too long). The previous point was meant to pre-register the entity.
Even if serialization is not efficent and should be avoided, if you decided to store your Object as Lob in the database, just note that you have nothing to implement using EJB 3, the following code will perform serialization automatically.

@Lob
@Column(name = "name")
private XXX myObject;

Comments