ScheduledExecutionUtil { public static void scheduleWithJitter(Runnable runnable, ScheduledExecutorService exec, Duration interval) { final double jitter = ThreadLocalRandom.current().nextDouble(1.0 - JITTER, 1.0 + JITTER); final long delayMillis = (long) (jitter * interval.toMillis()); exec.schedule(() -> { runGuarded(runnable); scheduleWithJitter(runnable, exec, interval); }, delayMillis, MILLISECONDS); } private  ScheduledExecutionUtil(); static void scheduleWithJitter(Runnable runnable, ScheduledExecutorService exec, Duration interval);  }
@Test public void shouldScheduleWithJitter() { var executionTimes = new ArrayList<Integer>(); var time = new AtomicInteger(); Runnable runnable = () -> executionTimes.add(time.get()); scheduleWithJitter(runnable, SCHEDULER, INTERVAL); for (int i = 0; i < 10000; i++) { time.set(i); SCHEDULER.tick(1, SECONDS); } var statistics = IntStream.range(0, executionTimes.size() - 1) .map(i -> executionTimes.get(i + 1) - executionTimes.get(i)) .summaryStatistics(); assertThat(statistics.getAverage(), is(closeTo(INTERVAL.getSeconds(), 3))); assertThat((double) statistics.getMin(), is( both(greaterThanOrEqualTo(INTERVAL.getSeconds() * (1.0 - JITTER))) .and(lessThanOrEqualTo(INTERVAL.getSeconds() * 1.0)))); assertThat((double) statistics.getMax(), is( both(greaterThanOrEqualTo(INTERVAL.getSeconds() * 1.0)) .and(lessThanOrEqualTo(INTERVAL.getSeconds() * (1.0 + JITTER))))); }