code: @SneakyThrows
  	private void launcher() {
  		while(!shuttingDown) {
  			Long timeBeforeNextExecution = null;
  			synchronized (this) {
  				if(nextExecutionsOrder.size() > 0) {
  					timeBeforeNextExecution = nextExecutionsOrder.get(0).nextExecutionTimeInMillis()
  						- timeProvider.currentTime();
  				}
  			}
  
  			if(timeBeforeNextExecution == null || timeBeforeNextExecution > 0L) {
  				synchronized (launcherNotifier) {
  					if(shuttingDown) {
  						return;
  					}
  					// If someone has notified the launcher
  					// then the launcher must check again the next job to execute.
  					// We must be sure not to miss any changes that would have
  					// happened after the timeBeforeNextExecution calculation.
  					if(launcherNotifier.get()) {
  						if(timeBeforeNextExecution == null) {
  							launcherNotifier.wait();
  						} else {
  							launcherNotifier.wait(timeBeforeNextExecution);
  						}
  					}
  					launcherNotifier.set(true);
  				}
  			} else {
  				synchronized (this) {
  					if(shuttingDown) {
  						return;
  					}
  
  					if(nextExecutionsOrder.size() > 0) {
  						Job jobToRun = nextExecutionsOrder.remove(0);
  						jobToRun.status(JobStatus.READY);
  						jobToRun.runningJob(() -> runJob(jobToRun));
  						if(threadPoolExecutor.getActiveCount() == threadPoolExecutor.getMaximumPoolSize()) {
  							logger.warn(
  								"Job thread pool is full, either tasks take too much time to execute"
  								+ " or either the thread pool is too small"
  							);
  						}
  						threadPoolExecutor.execute(jobToRun.runningJob());
  					}
  				}
  			}
  		}
  	}
docs: The daemon that will be in charge of placing the jobs in the thread pool
  when they are ready to be executed.