Added demo and updated some javadoc and some mechanics.
This commit is contained in:
parent
865bdaf35c
commit
020d8e2258
|
@ -4,6 +4,7 @@ import nl.andrewlalis.simply_scheduled.schedule.Task;
|
|||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
@ -18,18 +19,26 @@ public class BasicScheduler extends Thread implements Scheduler {
|
|||
private final ExecutorService executorService;
|
||||
private boolean running = false;
|
||||
|
||||
public BasicScheduler(Clock clock) {
|
||||
public BasicScheduler(Clock clock, ExecutorService executorService) {
|
||||
this.clock = clock;
|
||||
this.tasks = new PriorityBlockingQueue<>();
|
||||
this.executorService = Executors.newWorkStealingPool();
|
||||
this.executorService = executorService;
|
||||
}
|
||||
|
||||
public BasicScheduler() {
|
||||
this(Clock.systemDefaultZone());
|
||||
this(Clock.systemDefaultZone(), Executors.newWorkStealingPool());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a task to this scheduler's queue.
|
||||
* @param task The task to add.
|
||||
* @throws RuntimeException If a task is added while the scheduler is running.
|
||||
*/
|
||||
@Override
|
||||
public void addTask(Task task) {
|
||||
if (this.running) {
|
||||
throw new RuntimeException("Cannot add tasks to the basic scheduler while it is running.");
|
||||
}
|
||||
this.tasks.add(task);
|
||||
}
|
||||
|
||||
|
@ -40,13 +49,19 @@ public class BasicScheduler extends Thread implements Scheduler {
|
|||
try {
|
||||
Task nextTask = this.tasks.take();
|
||||
Instant now = this.clock.instant();
|
||||
long waitTime = nextTask.getSchedule().getNextExecutionTime(now).toEpochMilli() - now.toEpochMilli();
|
||||
Optional<Instant> optionalNextExecution = nextTask.getSchedule().getNextExecutionTime(now);
|
||||
if (optionalNextExecution.isEmpty()) {
|
||||
continue; // Skip if the schedule doesn't have a next execution planned.
|
||||
}
|
||||
long waitTime = optionalNextExecution.get().toEpochMilli() - now.toEpochMilli();
|
||||
if (waitTime > 0) {
|
||||
Thread.sleep(waitTime);
|
||||
}
|
||||
this.executorService.execute(nextTask.getRunnable());
|
||||
nextTask.getSchedule().markExecuted(this.clock.instant());
|
||||
if (nextTask.getSchedule().isRepeating()) {
|
||||
this.tasks.put(nextTask); // Put the task back in the queue.
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
this.setRunning(false);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package nl.andrewlalis.simply_scheduled;
|
||||
|
||||
import nl.andrewlalis.simply_scheduled.schedule.RepeatingSchedule;
|
||||
import nl.andrewlalis.simply_scheduled.schedule.Schedule;
|
||||
import nl.andrewlalis.simply_scheduled.schedule.Task;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
public class Demo {
|
||||
public static void main(String[] args) {
|
||||
Scheduler scheduler = new BasicScheduler();
|
||||
Schedule schedule = new RepeatingSchedule(ChronoUnit.MILLIS, 250);
|
||||
Runnable job = () -> System.out.println("Doing task: " + Instant.now().toString());
|
||||
scheduler.addTask(Task.of(job, schedule));
|
||||
scheduler.start();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package nl.andrewlalis.simply_scheduled.schedule;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A daily schedule plans for the execution of a task once per day, at a
|
||||
|
@ -20,13 +21,13 @@ public class DailySchedule implements Schedule {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Instant getNextExecutionTime(Instant referenceInstant) {
|
||||
public Optional<Instant> getNextExecutionTime(Instant referenceInstant) {
|
||||
ZonedDateTime currentTime = referenceInstant.atZone(this.zoneId);
|
||||
LocalDate currentDay = LocalDate.from(referenceInstant);
|
||||
ZonedDateTime sameDayExecution = currentDay.atTime(this.time).atZone(this.zoneId);
|
||||
if (sameDayExecution.isBefore(currentTime)) {
|
||||
return sameDayExecution.toInstant();
|
||||
return Optional.of(sameDayExecution.toInstant());
|
||||
}
|
||||
return sameDayExecution.plusDays(1).toInstant();
|
||||
return Optional.of(sameDayExecution.plusDays(1).toInstant());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.time.Instant;
|
|||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* An hourly schedule is used to execute a task once per hour, at a specific
|
||||
|
@ -26,12 +27,12 @@ public class HourlySchedule implements Schedule {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Instant getNextExecutionTime(Instant referenceInstant) {
|
||||
public Optional<Instant> getNextExecutionTime(Instant referenceInstant) {
|
||||
ZonedDateTime currentTime = referenceInstant.atZone(this.zoneId);
|
||||
int currentMinute = currentTime.getMinute();
|
||||
if (currentMinute < this.minute) {
|
||||
return currentTime.truncatedTo(ChronoUnit.MINUTES).plusMinutes(this.minute - currentMinute).toInstant();
|
||||
return Optional.of(currentTime.truncatedTo(ChronoUnit.MINUTES).plusMinutes(this.minute - currentMinute).toInstant());
|
||||
}
|
||||
return currentTime.plusHours(1).plusMinutes(this.minute).truncatedTo(ChronoUnit.MINUTES).toInstant();
|
||||
return Optional.of(currentTime.plusHours(1).plusMinutes(this.minute).truncatedTo(ChronoUnit.MINUTES).toInstant());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.time.Instant;
|
|||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
public class MinutelySchedule implements Schedule {
|
||||
|
||||
|
@ -20,12 +21,12 @@ public class MinutelySchedule implements Schedule {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Instant getNextExecutionTime(Instant referenceInstant) {
|
||||
public Optional<Instant> getNextExecutionTime(Instant referenceInstant) {
|
||||
ZonedDateTime currentTime = referenceInstant.atZone(this.zoneId);
|
||||
int currentSecond = currentTime.getSecond();
|
||||
if (currentSecond >= this.second) {
|
||||
return currentTime.plusMinutes(1).withSecond(this.second).truncatedTo(ChronoUnit.SECONDS).toInstant();
|
||||
return Optional.of(currentTime.plusMinutes(1).withSecond(this.second).truncatedTo(ChronoUnit.SECONDS).toInstant());
|
||||
}
|
||||
return currentTime.withSecond(this.second).truncatedTo(ChronoUnit.SECONDS).toInstant();
|
||||
return Optional.of(currentTime.withSecond(this.second).truncatedTo(ChronoUnit.SECONDS).toInstant());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewlalis.simply_scheduled.schedule;
|
|||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A schedule which repeatedly executes a task at a regular interval specified
|
||||
|
@ -30,11 +31,11 @@ public class RepeatingSchedule implements Schedule {
|
|||
* @return The next instant to execute the task at.
|
||||
*/
|
||||
@Override
|
||||
public Instant getNextExecutionTime(Instant referenceInstant) {
|
||||
public Optional<Instant> getNextExecutionTime(Instant referenceInstant) {
|
||||
if (this.lastExecution == null) {
|
||||
this.lastExecution = referenceInstant;
|
||||
}
|
||||
return this.lastExecution.plus(multiple, unit);
|
||||
return Optional.of(this.lastExecution.plus(multiple, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewlalis.simply_scheduled.schedule;
|
|||
import nl.andrewlalis.simply_scheduled.Scheduler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A schedule is used by a {@link Scheduler} to determine how long to wait for
|
||||
|
@ -16,9 +17,10 @@ public interface Schedule {
|
|||
*
|
||||
* @param referenceInstant The instant representing the current time.
|
||||
* @return An instant in the future indicating the next time at which a task
|
||||
* using this schedule should be executed.
|
||||
* using this schedule should be executed. If the optional is empty, it
|
||||
* indicates that there are no planned execution times.
|
||||
*/
|
||||
Instant getNextExecutionTime(Instant referenceInstant);
|
||||
Optional<Instant> getNextExecutionTime(Instant referenceInstant);
|
||||
|
||||
/**
|
||||
* This method is called on the schedule as an indication that the scheduler
|
||||
|
@ -28,4 +30,13 @@ public interface Schedule {
|
|||
default void markExecuted(Instant instant) {
|
||||
// Default no-op.
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether tasks executed in accordance with this schedule should be
|
||||
* re-queued again for another execution. Defaults to <code>true</code>.
|
||||
* @return True if this schedule is repeating, or false otherwise.
|
||||
*/
|
||||
default boolean isRepeating() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package nl.andrewlalis.simply_scheduled.schedule;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A schedule in which a discrete number of execution times are defined at
|
||||
* initialization.
|
||||
*/
|
||||
public class SpecificInstantSchedule implements Schedule {
|
||||
private final List<Instant> executionTimes;
|
||||
|
||||
public SpecificInstantSchedule(Instant... executionTimes) {
|
||||
this(new ArrayList<>(Arrays.asList(executionTimes)));
|
||||
}
|
||||
|
||||
public SpecificInstantSchedule(ZonedDateTime... zonedDateTimes) {
|
||||
this(Arrays.stream(zonedDateTimes).map(ZonedDateTime::toInstant).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private SpecificInstantSchedule(List<Instant> executionTimes) {
|
||||
this.executionTimes = executionTimes;
|
||||
Collections.sort(this.executionTimes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Instant> getNextExecutionTime(Instant referenceInstant) {
|
||||
while (!this.executionTimes.isEmpty()) {
|
||||
Instant nextExecutionTime = this.executionTimes.remove(0);
|
||||
if (nextExecutionTime.isBefore(referenceInstant)) {
|
||||
return Optional.of(nextExecutionTime);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package nl.andrewlalis.simply_scheduled.schedule;
|
|||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A task consists of a runnable job, and a schedule that defines precisely when
|
||||
|
@ -19,8 +20,8 @@ public class Task implements Comparable<Task>{
|
|||
this.schedule = schedule;
|
||||
}
|
||||
|
||||
public Task(Runnable runnable, Schedule schedule) {
|
||||
this(Clock.systemDefaultZone(), runnable, schedule);
|
||||
public static Task of(Runnable runnable, Schedule schedule) {
|
||||
return new Task(Clock.systemDefaultZone(), runnable, schedule);
|
||||
}
|
||||
|
||||
public Runnable getRunnable() {
|
||||
|
@ -34,6 +35,13 @@ public class Task implements Comparable<Task>{
|
|||
@Override
|
||||
public int compareTo(Task o) {
|
||||
Instant now = clock.instant();
|
||||
return this.schedule.getNextExecutionTime(now).compareTo(o.getSchedule().getNextExecutionTime(now));
|
||||
Optional<Instant> t1 = this.schedule.getNextExecutionTime(now);
|
||||
Optional<Instant> t2 = o.getSchedule().getNextExecutionTime(now);
|
||||
|
||||
if (t1.isEmpty() && t2.isEmpty()) return 0;
|
||||
if (t1.isPresent() && t2.isEmpty()) return 1;
|
||||
if (t1.isEmpty()) return -1;
|
||||
|
||||
return t1.get().compareTo(t2.get());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.time.Clock;
|
|||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
@ -20,7 +21,7 @@ public class SchedulerTest {
|
|||
@Test
|
||||
void testSchedule() {
|
||||
Clock clock = Clock.fixed(Instant.now().truncatedTo(ChronoUnit.MINUTES), ZoneOffset.UTC);
|
||||
Scheduler scheduler = new BasicScheduler(clock);
|
||||
Scheduler scheduler = new BasicScheduler(clock, Executors.newSingleThreadExecutor());
|
||||
int secondsLeft = 4;
|
||||
AtomicBoolean flag = new AtomicBoolean(false);
|
||||
Runnable taskRunnable = () -> {
|
||||
|
@ -45,15 +46,14 @@ public class SchedulerTest {
|
|||
|
||||
@Test
|
||||
void testRepeatingSchedule() {
|
||||
Scheduler scheduler = new BasicScheduler();
|
||||
Scheduler scheduler = new BasicScheduler(Clock.systemUTC(), Executors.newWorkStealingPool());
|
||||
Schedule schedule = new RepeatingSchedule(ChronoUnit.SECONDS, 1);
|
||||
AtomicInteger value = new AtomicInteger(0);
|
||||
Runnable taskRunnable = () -> {
|
||||
value.set(value.get() + 1);
|
||||
System.out.println("\tExecuted task.");
|
||||
};
|
||||
Task task = new Task(taskRunnable, schedule);
|
||||
scheduler.addTask(task);
|
||||
scheduler.addTask(Task.of(taskRunnable, schedule));
|
||||
assertEquals(0, value.get());
|
||||
scheduler.start();
|
||||
System.out.println("Waiting 3.5 seconds for 3 iterations.");
|
||||
|
|
Loading…
Reference in New Issue