I'm in doubt if the solution below is correct for the following:
- in a multi-user application any user can start processing of given data by clicking a button
- processing takes long time thus it should be executed asynchronously to not block GUI
- if one user has already started the processing, the other requests should be rejected till it completes
Below is the code used to solve this:
public class Processor {
private final ExecutorService execService = Executors.newSingleThreadExecutor();
private final Object monitor = new Object();
private AtomicReference<Task> runningTask = new AtomicReference<>(null);
public Optional<CompletableFuture<String>> processDataAsync(String data) {
if (runningTask.get() != null)
return Optional.empty(); //rejecting data process request because another request is already being served
synchronized (monitor) {
if (runningTask.get() != null)
return Optional.empty();
CompletableFuture<String> f = new CompletableFuture<>();
f.whenComplete((r, e) -> runningTask.set(null)); //when processing completes, another data process request can be accepted
Task task = new Task(f, data);
runningTask.set(task);
execService.submit(task);
return Optional.of(f);
}
}
}
Task is Runnable as below:
public class Task implements Runnable {
private final CompletableFuture<String> result;
private final String data;
public Task(CompletableFuture<String> result, String data) {
this.result = result;
this.data = data;
}
@Override
public void run() {
String processingResult = processData(data); //does some blocking stuff with data, returning result of processing
result.complete(processingResult);
}
}
What confuses me here is the synchronization (i.e. blocking) in processDataAsync. I understand that blocking here is very short and not critical, but shouldn't asynchronous method be always implemented without blocking? If so, I can't imagine how "single processing" can be achieved without synchronization.
Perhaps I’ve misunderstood the problem, but it seems you are over complicating the situation. Rather than keeping track of the task, keep track of the
Futurereturned byExecutorService#submit. AFutureobject is your tether leading back to the task being executed.Define a member field for the
Future.Test the
Futurewhen request to process is made. CallFuture#isDoneto test. Javadoc says:TaskMastersolutionIn various comments, you presented more details of your problem.
You want to submit tasks from various threads to a single object. Let's call that object
TaskMasterfor clarity. ThatTaskMasterinstance tracks whether its nested executor service is currently working on a task or not.Optional< Future >.Optional< Future >.Since the code shown above discussed here will be accessed across threads, we must protect the
Future future ;in a thread-safe manner. One easy way to do that is to marksynchronizedon the one and only method for tendering a task to theTaskMaster.Usage shown next. Beware: Multithreaded calls to
System.out.printlndo not always appear on the console chronologically. Always include, and inspect, timestamps to verify the order.When run.
Custom
ExecutorServiceWhile that
TaskMastercode above works, and offers theOptionalobjects you asked for, I would recommend another approach.You can make your own version of an
ExecutorService. Your implementation could do something similar to what we saw above, tracking a single task’s execution.Rather than returning an
Optional< Future >, the more orthodox approach would be to provide asubmitmethod implementation that either:Futureif the tendered task can be immediately executed, or …RejectedExecutionExceptionbecause a task is already running.This behavior is defined in the Javadoc of
ExecutorService. Any methods of your which tender tasks to this custom executor service would trap for this exception rather than examine anOptional.In other words, to modify an excerpt from your Comment:
With this custom executor service, the calling programmer has less to learn. The calling programmer would not need to understand the semantics of the
TaskMasterclass, they need to understand only the commonExecutorServicebehavior.Tip: The
AbstractExecutorServiceclass might make creating your own executor service easier.