Search code examples
javaspring-bootjava-17

Right way of implementing a controllable scheduler


For my design I need a controllable schedular. Spring boot offers an @Scheduled annotation but that is more simplified and I do not have granular control.

So I wanted to implement my own scheduler manually.

This is the class I created:

@Slf4j
@Service
public class JobExecutor {

    private static ScheduledExecutorService jobExecutor;

    private static Environment env;
    
    @Autowired
    private JobExecutor(Environment env) {
        JobExecutor.env = env;
    }

    public static ScheduledExecutorService INSTANCE() {
        if (null == jobExecutor) {
            synchronized (JobExecutor.class) {
                if (null == jobExecutor) {
                    jobExecutor = Executors.newScheduledThreadPool(
                            Integer.parseInt(Objects.requireNonNull(env.getProperty("scheduler.jobs"))));
                }
            }
        }
        return jobExecutor;
    }
}

With this approach I could simply call the static method to get a single instance.

Is this correct approach for a schedular? I need to start and stop and shutdown the jobs. I tried guava AbstractScheduledService but that does not seem to be working.


Solution

  • This is not the correct approach for creating a singleton, because double checked locking is broken. You're using Spring, so a) your JobExecutor will be a singleton anyway, and b) will only be created if it is needed. You might as well, therefore, create your executor instance in the constructor and get rid of those static methods.

    Even better, you could create schedulers as named beans, and then inject them into classes where you want them:

    @Configuration
    public class ExecutorConfiguration {
        @Bean
        public ScheduledExecutorService jobExecutor(@Value("${scheduler.jobs}") jobs) {
            return Executors.newScheduledThreadPool(jobs);
        }
    }
    

    This says that whenever another component needs a ScheduledExecutorService, Spring should call this jobExecutor() method; Spring will automatically populate the jobs parameter from the scheduler.jobs property because of the @Value.

    You can then inject your executor wherever you need it, for example with constructor injection (handily you're already using Lombok, so the amount of boilerplate is minimised):

    @Service
    @AllArgsConstructor
    public class MyThingThatNeedsAScheduler {
        private final ScheduledExecutorService jobExecutor;
    
        // methods here...
    }
    

    You can also use setter or member injection, if you want.