I am not sure if this is the best way to do it, but here is a working way i came up with.
You would need three types of tasks:
- the task itself, which has to run periodically - the important task
- another task which will cancel it's execution, i'll call it kill task from now on.
- task to submit the important task for execution - the submit task, this is what you will schedule with the
ScheduledExecutorService
to run periodically
The idea is, when your important task starts execution, it will schedule the kill task to run 5 seconds later. In order to achieve that, it needs to know its' Future
instance. And for that, it needs the submit task to do the submission and let it know of its' Future
.
Here is important task
:
import java.time.LocalTime;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ImportantTask implements Runnable {
//that's just to make it easier to track what happens with each task
private static long NEXT_ID = 1;
//needed to schedule kill task
private final ScheduledExecutorService executorService;
private final long taskId;
private Future<?> thisTaskFuture;
//only needed to know when to cancel all tasks and end test
private CountDownLatch countDownLatch;
public ImportantTask(ScheduledExecutorService executorService) {
this.executorService = executorService;
this.taskId = NEXT_ID++;
}
@Override
public void run() {
//make half the tasks take too long
long executionTimeSeconds = this.taskId % 2 == 0 ? 6 : 4;
System.out.println(this.getLogMessage("started"));
CancelFutureTask cancelFutureTask = new CancelFutureTask(this.thisTaskFuture);
//this schedules the kill task for single execution, after 5 seconds delay
this.executorService.schedule(cancelFutureTask, 5, TimeUnit.SECONDS);
try {
//simulating long execution
Thread.sleep(executionTimeSeconds * 1_000);
} catch (InterruptedException exc) {
System.out.println(this.getLogMessage("interrupted"));
//might need to check Thread.currentThread().isInterrupted(), depends on your exact case
return;
} finally {
this.countDownLatch.countDown();
}
//the task is already finished, even if cancel is called, it has no effect
System.out.println(this.getLogMessage("finished"));
}
private String getLogMessage(String taskStatus) {
return String.format("Task with id - %d, %s at %s", this.taskId, taskStatus, LocalTime.now());
}
public void setThisTaskFuture(Future<?> thisTaskFuture) {
this.thisTaskFuture = thisTaskFuture;
}
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
}
The CountDownLatch is here only to make it easier to stop main method after few executions, you probably do not need it. Take note of the comments in the code.
The kill task
is quite simple:
import java.util.concurrent.Future;
public class CancelFutureTask implements Runnable {
private final Future<?> future;
private final boolean mayInterrupt;
public CancelFutureTask(Future<?> future, boolean mayInterrupt) {
this.future = future;
this.mayInterrupt = mayInterrupt;
}
public CancelFutureTask(Future<?> future) {
this(future, true);
}
@Override
public void run() {
this.future.cancel(this.mayInterrupt);
}
}
It just cancels a Future
and will have no effect if the future has already finished execution.
And submit task
:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
public class SubmitNewImportantTask implements Runnable {
private final ScheduledExecutorService executorService;
private final CountDownLatch countDownLatch;
public SubmitNewImportantTask(ScheduledExecutorService executorService, CountDownLatch countDownLatch) {
this.executorService = executorService;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
ImportantTask importantTask = new ImportantTask(this.executorService);
//obtain Future instance
Future<?> future = this.executorService.submit(importantTask);
//let the task know of its Future instance
importantTask.setThisTaskFuture(future);
importantTask.setCountDownLatch(this.countDownLatch);
}
}
It submits important task
for execution, obtains the Future
instance and sets it in important task
.
And the main method i used for testing.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class FuturesMain {
public static void main(String[] args) throws Exception {
CountDownLatch count = new CountDownLatch(6);
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(10);
SubmitNewImportantTask submitTask = new SubmitNewImportantTask(executorService, count);
executorService.scheduleAtFixedRate(submitTask, 0, 10, TimeUnit.SECONDS);
count.await();
executorService.shutdown();
boolean allTerminated = executorService.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("All terminated - " + allTerminated);
}
}