Codementor Events

Java Scheduler

Published Aug 14, 2019

In this article , we are going to cover the below topics on Scheduler:

  • Schedular in Java
  • SchedularConfigurer vs @Scheduled
  • Changing cron expression dynamically
  • dependency Execution between two tasks.

Scheduling a Task in Java

Scheduler is to schedule a thread or task to execute at a certain period of time or periodically at a fixed interval . There are multiple ways of scheduling a task in Java .

  • Java library java.util.TimerTask
  • java.util.concurrent.ScheduledExecutorService
  • Quartz Schedular
  • org.springframework.scheduling.TaskScheduler

TimerTask is executed by one execution demon thread. Any delay in a task can delay the other task in schedule . Hence it is not a viable option when multiple task needs to be executed asynchronously at a certain time .

Lets take an example :

package com.example.timerExamples;

import java.util.Timer;

public class ExecuteTimer {

  public static void main(String[] args){
                TimerExample te1=new TimerExample("Task1");
    TimerExample te2=new TimerExample("Task2");
    
    Timer t=new Timer();
    t.scheduleAtFixedRate(te1, 0,5*1000);
    t.scheduleAtFixedRate(te2, 0,1000);
   }
}

public class TimerExample extends TimerTask{

private String name ;
public TimerExample(String n){
  this.name=n;
}

@Override
public void run() {
    System.out.println(Thread.currentThread().getName()+" "+name+" the task has executed successfully "+ new Date());
    if("Task1".equalsIgnoreCase(name)){
      try {
        Thread.sleep(10000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
}

Output :

Timer-0  Task1 the task has executed successfully Wed Nov 14 14:32:49 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task1 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task1 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018

In the above execution it is clear that task 2 gets stuck because the thread that is handling task1 is going to sleep for 10 secs. Hence there is only one demon thread that is working on both task 1 and task 2 and if one gets hit all the task will be pushed back .

ScheduledExecutorService and TaskScheduler works in a same manner . The only difference is former is Java library and the later is spring framework . So if the application is on spring , the TaskScheduler can be a better option to schedule jobs .

Now lets see the usage of TaskScheduler interface and we can use it in spring .

SchedularConfigurer vs @Scheduled

Spring provides annotation based scheduling with the help of @Scheduled .

The threads are handled by Spring framework and we will not have any control on the threads that will work on the tasks . Lets take the below example :

@Configuration
@EnableScheduling
public class ScheduledConfiguration {
  
    @Scheduled(fixedRate = 5000)
      public void executeTask1() {
          System.out.println(Thread.currentThread().getName()+" The Task1 executed at "+ new Date());
          try {
        Thread.sleep(10000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      }
          @Scheduled(fixedRate = 1000)
      public void executeTask2() {
          System.out.println(Thread.currentThread().getName()+" The Task2 executed at "+ new Date());
      }
}

Output:

scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task1 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task1 executed at Wed Nov 14 14:23:09 GMT 2018

There is one thread scheduling-1 which is handling both task1 and task2 . The moment task1 goes to sleep for 10 s , task 2 also waits for it . Hence we understand that if there are 2 jobs running at same time , one will wait for another to complete .

Now we will try writing a scheduler task where we want to execute task1 and task2 asynchronously . There will be a pool of threads and we will schedule each tasks in ThreadPoolTaskScheduler . Class needs to implement SchedulingConfigurer interface . It gives more control on the scheduler threads as compared to @Scheduled .

@Configuration
@EnableScheduling
public class ScheduledConfiguration implements SchedulingConfigurer {
  
  TaskScheduler taskScheduler;
  private ScheduledFuture<?> job1;
  private ScheduledFuture<?> job2;
  @Override
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    
    ThreadPoolTaskScheduler threadPoolTaskScheduler =new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(10);// Set the pool of threads
    threadPoolTaskScheduler.setThreadNamePrefix("scheduler-thread");
    threadPoolTaskScheduler.initialize();
    job1(threadPoolTaskScheduler);// Assign the job1 to the scheduler
    job2(threadPoolTaskScheduler);// Assign the job1 to the scheduler
    this.taskScheduler=threadPoolTaskScheduler;// this will be used in later part of the article during refreshing the cron expression dynamically
    taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
    
  }
       
        private void job1(TaskScheduler scheduler) {
               job1 = scheduler.schedule(new Runnable() {
               @Override
               public void run() {
        System.out.println(Thread.currentThread().getName() + " The Task1 executed at " + new Date());
        try {
          Thread.sleep(10000);
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
               }, new Trigger() {
                    @Override
                    public Date nextExecutionTime(TriggerContext triggerContext) {
        String cronExp = "0/5 * * * * ?";// Can be pulled from a db .
                     return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
                  }
                });
           }
    
        private void job2(TaskScheduler scheduler){
    job2=scheduler.schedule(new Runnable(){
                   @Override
      public void run() {
        System.out.println(Thread.currentThread().getName()+" The Task2 executed at "+ new Date());
      }
                    }, new Trigger(){
                        @Override
      public Date nextExecutionTime(TriggerContext triggerContext) {
        String cronExp="0/1 * * * * ?";//Can be pulled from a db . This will run every minute
        return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
      }
      
    });
              }
   }

Output:

scheduler-thread1 The Task2 executed at Wed Nov 14 15:02:46 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:02:47 GMT 2018
scheduler-thread3 The Task2 executed at Wed Nov 14 15:02:48 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:02:49 GMT 2018
scheduler-thread1 The Task2 executed at Wed Nov 14 15:02:50 GMT 2018
scheduler-thread7 The Task1 executed at Wed Nov 14 15:02:50 GMT 2018
scheduler-thread3 The Task2 executed at Wed Nov 14 15:02:51 GMT 2018
scheduler-thread5 The Task2 executed at Wed Nov 14 15:02:52 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:02:53 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:54 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:55 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:56 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:57 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:58 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:59 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:03:00 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:03:01 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:03:02 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:03:03 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:03:04 GMT 2018
scheduler-thread10 The Task2 executed at Wed Nov 14 15:03:05 GMT 2018
scheduler-thread8 The Task1 executed at Wed Nov 14 15:03:05 GMT 2018

I am creating 2 jobs: job1 and job2 . Then scheduling it using TaskScheduler. This time I am using a Cron expression to schedule the job1 at every 5 sec and job2 at every second . Job1 gets stuck for 10 sec and we will see job2 still running smoothly without interruption . We see that both task1 and task 2 are being handled by a pool of threads which is created using ThreadPoolTaskScheduler

Changing cron expression dynamically

we can always keep the cron expression in a property file in Spring config . If Spring Config server is not available , we can also fetch it from DB . Any update in the cron expression will update the Scheduler . But in order to cancel the current schedule and execute the new schedule we can expose an API to refresh the cron job :

public void refreshCronSchedule(){
    
    if(job1!=null){
      job1.cancel(true);
      scheduleJob1(taskScheduler);
    }
    
    if(job2!=null){
      job2.cancel(true);
      scheduleJob2(taskScheduler);
    }
  }

Invoke the method from any controller to refresh the cron schedule.

Dependency Execution between two tasks.

So far we know that we can execute the jobs asynchronously using TaskScheduler and Schedulingconfigurer interface. Now lets say we have a job1 which runs for an hour at 1 am night and we have job2 which should run at 2 am night . But job2 should not start unless job1 has completed . We also have other list of jobs which can run between 1 am and 2 am and are independent of other jobs .

Lets see how we can create a dependency between job1 and job2 , yet run all jobs asynchronously at scheduled time .

declare a volatile variable :

private volatile boolean job1Flag=false;

 private void scheduleJob1(TaskScheduler scheduler) {
           job1 = scheduler.schedule(new Runnable() {
             @Override
       public void run() {           
                System.out.println(Thread.currentThread().getName() + " The Task1 executed at " + new Date());
        try {
          Thread.sleep(10000);
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
                  
               job1Flag=true;// setting the flag true to mark it complete
           }
         }, new Trigger() {
        @Override
      public Date nextExecutionTime(TriggerContext triggerContext) {
                           String cronExp = "0/5 * * * * ?";// Can be pulled from a db 
                          return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
                        }

                 });

      }
private void scheduleJob2(TaskScheduler scheduler) {
      job2=scheduler.schedule(new Runnable(){

       @Override
       public void run() {
         synchronized(this){
           while(!job1Flag){
               System.out.println(Thread.currentThread().getName()+" waiting for job1 to complete to execute "+ new Date());
             try {
                  wait(1000);// add any number of seconds to wait 
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                  }
           }
         }

          System.out.println(Thread.currentThread().getName()+" The Task2 executed at "+ new Date());
          job1Flag=false;
      }
     }, new Trigger(){
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
            String cronExp="0/5 * * * * ?";//Can be pulled from a db . This will run every minute
            return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
      }
    });
  }

scheduler-thread2 The Task1 executed at Wed Nov 14 16:30:50 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:51 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:52 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:53 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:54 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:55 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:56 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:57 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:58 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:59 GMT 2018
scheduler-thread1 The Task2 executed at Wed Nov 14 16:31:00 GMT 2018
scheduler-thread2 The Task1 executed at Wed Nov 14 16:31:05 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:05 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:06 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:07 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:08 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:09 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:10 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:11 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:12 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:13 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:14 GMT 2018
scheduler-thread3 The Task2 executed at Wed Nov 14 16:31:15 GMT 2018
scheduler-thread1 The Task1 executed at Wed Nov 14 16:31:20 GMT 2018

Using a volatile boolean flag so that it is not cached in thread-local but is saved in main memory and can be used by all the threads in the pool . Based on the flag , job2 waits indefinitely till job1 completes . Now if job1 hangs there is a chance job2 waits indefinitely .

Conclusion

There are various ways of scheduling and now we know when we can use a spring scheduler and a java scheduler api . And how to use the spring scheduler api and control the threads in the pool .

Discover and read more posts from Joydip Kumar
get started