The sbridges solution is still the cleanest so far, except that you mentioned that it is not responsible for handling the number of executions of Runnable itself. This should not concern this; instead, repetitions should be a parameter of the class that handles the planning. To achieve this, I would suggest the following design, which introduces a new artist class for Runnables . The class provides two publicly available task scheduling methods, which are standard Runnables , with finite or infinite repetition. The same Runnable can be passed for finite and infinite planning, if necessary (which is not possible with all the proposed solutions that extend the Runnable class to provide finite repetitions). The end repetition cancellation handling is fully encapsulated in the scheduler class:
class MaxNScheduler { public enum ScheduleType { FixedRate, FixedDelay } private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); public ScheduledFuture<?> scheduleInfinitely(Runnable task, ScheduleType type, long initialDelay, long period, TimeUnit unit) { return scheduleNTimes(task, -1, type, initialDelay, period, unit); } public ScheduledFuture<?> scheduleNTimes(Runnable task, int repetitions, ScheduleType type, long initialDelay, long period, TimeUnit unit) { RunnableWrapper wrapper = new RunnableWrapper(task, repetitions); ScheduledFuture<?> future; if(type == ScheduleType.FixedDelay) future = executorService.scheduleWithFixedDelay(wrapper, initialDelay, period, TimeUnit.MILLISECONDS); else future = executorService.scheduleAtFixedRate(wrapper, initialDelay, period, TimeUnit.MILLISECONDS); synchronized(wrapper) { wrapper.self = future; wrapper.notify();
In fairness, it follows that the repetition control logic still exists with Runnable , but it is Runnable completely internal to MaxNScheduler , while the Runnable task that was submitted for scheduling should not concern itself with the nature of scheduling. Also, this problem can be easily ported to the scheduler, if necessary, by providing some callback every time RunnableWrapper.run executed. This will complicate the code a bit and add the need to save some RunnableWrapper map and corresponding repetitions, so I decided to save the counters in the RunnableWrapper class.
I also added some wrapper synchronization when setting up self. This is necessary as theoretically, when the completion of execution, I may not have been assigned yet (theoretical scenario, but only for one repetition).
The cancellation is handled gracefully without throwing an InterruptedException , and if the next round is executed before it is canceled, RunnableWrapper will not call the base Runnable .