Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Working With the Java Scheduler

DZone 's Guide to

Working With the Java Scheduler

Using the Java scheduler has never been easier.

· Java Zone ·
Free Resource

In this article, we are going to cover the following topics pertaining to the Java Scheduler:

  • Scheduling a task in Java
  • SchedularConfigurer vs.@Scheduled
  • Changing the cron expression dynamically
  • Dependency execution between two tasks

Scheduling a Task in Java

The scheduler is used to schedule a thread or task that executes at a certain period of time or periodically at a fixed interval. There are multiple ways to schedule a task in Java.

  •  java.util.TimerTask 
  •  java.util.concurrent.ScheduledExecutorService
  •  Quartz Scheduler
  •  org.springframework.scheduling.TaskScheduler

TimerTaskis executed by a demon thread. Any delay in a task can delay the other task in a schedule. Hence, it is not a viable option when multiple tasks need to be executed asynchronously at a certain time.

Let's look at 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 tasks will be pushed back.

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

Now, let's see the usage of the TaskScheduler interface and we can use it in Spring.

SchedularConfigurer Vs. @Scheduled

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

The threads are handled by the Spring framework, and we will not have any control over the threads that will work on the tasks. Let's take a look at the example below:

@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 seconds, task 2 also waits for it. Hence, if there are two jobs running at the 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 task in the ThreadPoolTaskScheduler. The class needs to implement the SchedulingConfigurer interface. It gives more control to 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 two jobs: job1 and job2. Then, I will be scheduling it using TaskScheduler. This time, I am using a Cron expression to schedule the job1 at every five-second interval and job2 every second. Job1 gets stuck for 10 seconds 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 a Cron Expression Dynamically

We can always keep the cron expression in a property file using the Spring config. If the Spring Config server is not available, we can also fetch it from theDB. Any update of 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);
  }
}


Additionally, you can 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 the TaskScheduler and Schedulingconfigurer interface. Now, let's say we have job1 that runs for an hour at 1 am and job2 that runs at 2 am. But, job2 should not start unless job1 is complete. We also have another list of jobs that can run between 1 and 2 am and are independent of other jobs.

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

First, let's 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


We chose to use a volatile boolean flag so that it is not cached in the thread-local but is saved in the main memory and can be used by all threads in the pool. Based on the flag, job2 waits indefinitely until job1 is complete. Now, if job1 hangs, there is a chance that job2 will wait indefinitely.

Conclusion

There are various ways to schedule in Java, and now, we know how to use the scheduler API and the Spring scheduler API to control threads in the pool.

Topics:
scheduler without cron ,spring 5 ,chron ,java ,tutorial ,scheduler ,spring scheduler ,spring config

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}