Created a static ExecutorService object and used it to execute an async task with input in a spring boot rest controller. not able to process any request after invoking async task for 3 times.
TaskManager.java
public class TaskManager {
static ExecutorService e = Executors.newFixedThreadPool(4);
private TaskManager(){
}
public static void addAsyncTask(AsyncTaskWrapper task){
e.submit(task);
}
}
public class AsyncTaskWrapper implements Callable {
Object input,output;
Callable callback;
AsyncTask task;
public Object getInput() {
return input;
}
public void setInput(Object input) {
this.input = input;
}
public Object getOutput() {
return output;
}
public AsyncTask getTask() {
return task;
}
public void setTask(AsyncTask task) {
this.task = task;
}
public Callable getCallback() {
return callback;
}
public void setCallback(Callable callback) {
this.callback = callback;
}
public AsyncTaskWrapper(AsyncTask task, Callable callback){
this.callback = callback;
this.task = task;
}
#Override
public Object call() throws Exception {
System.out.println("Started");
output = task.execute(input);
callback.call();
System.out.println("call completed");
return output;
}
}
RESTControllers.java - need to execute a task in async with input for a RESTEndpoint.
AsyncTaskWrapper task = new AsyncTaskWrapper(
(list) -> {
List l = (List) list;
try {
MailingUtils.sendRegistrationMail((String) l.get(0), (String) l.get(1));
} catch (MessagingException e) {
e.printStackTrace();
}
return null;
},
() -> {
System.out.println("Done");
return null;
});
task.setInput(objects);
TaskManager.addAsyncTask(task);
Thanks in advance
Related
As Brian Goetz states: "TrackingExecutor has an unavoidable race condition that could make it yield false positives: tasks that are identified as cancelled but actually completed. This arises because the thread pool could be shut down between when the last instruction of the task executes and when the pool records the task as complete."
TrackingExecutor:
/**
* TrackingExecutor
* <p/>
* ExecutorService that keeps track of cancelled tasks after shutdown
*
* #author Brian Goetz and Tim Peierls
*/
public class TrackingExecutor extends AbstractExecutorService {
private final ExecutorService exec;
private final Set<Runnable> tasksCancelledAtShutdown =
Collections.synchronizedSet(new HashSet<Runnable>());
public TrackingExecutor(ExecutorService exec) {
this.exec = exec;
}
public void shutdown() {
exec.shutdown();
}
public List<Runnable> shutdownNow() {
return exec.shutdownNow();
}
public boolean isShutdown() {
return exec.isShutdown();
}
public boolean isTerminated() {
return exec.isTerminated();
}
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
return exec.awaitTermination(timeout, unit);
}
public List<Runnable> getCancelledTasks() {
if (!exec.isTerminated())
throw new IllegalStateException(/*...*/);
return new ArrayList<Runnable>(tasksCancelledAtShutdown);
}
public void execute(final Runnable runnable) {
exec.execute(new Runnable() {
public void run() {
try {
runnable.run();
} finally {
if (isShutdown()
&& Thread.currentThread().isInterrupted())
tasksCancelledAtShutdown.add(runnable);
}
}
});
}
}
Then he creates Crawler which uses TrackingExecutor:
crawler:
/**
* WebCrawler
* <p/>
* Using TrackingExecutorService to save unfinished tasks for later execution
*
* #author Brian Goetz and Tim Peierls
*/
public abstract class WebCrawler {
private volatile TrackingExecutor exec;
#GuardedBy("this") private final Set<URL> urlsToCrawl = new HashSet<URL>();
private final ConcurrentMap<URL, Boolean> seen = new ConcurrentHashMap<URL, Boolean>();
private static final long TIMEOUT = 500;
private static final TimeUnit UNIT = MILLISECONDS;
public WebCrawler(URL startUrl) {
urlsToCrawl.add(startUrl);
}
public synchronized void start() {
exec = new TrackingExecutor(Executors.newCachedThreadPool());
for (URL url : urlsToCrawl) submitCrawlTask(url);
urlsToCrawl.clear();
}
public synchronized void stop() throws InterruptedException {
try {
saveUncrawled(exec.shutdownNow());
if (exec.awaitTermination(TIMEOUT, UNIT))
saveUncrawled(exec.getCancelledTasks());
} finally {
exec = null;
}
}
protected abstract List<URL> processPage(URL url);
private void saveUncrawled(List<Runnable> uncrawled) {
for (Runnable task : uncrawled)
urlsToCrawl.add(((CrawlTask) task).getPage());
}
private void submitCrawlTask(URL u) {
exec.execute(new CrawlTask(u));
}
private class CrawlTask implements Runnable {
private final URL url;
CrawlTask(URL url) {
this.url = url;
}
private int count = 1;
boolean alreadyCrawled() {
return seen.putIfAbsent(url, true) != null;
}
void markUncrawled() {
seen.remove(url);
System.out.printf("marking %s uncrawled%n", url);
}
public void run() {
for (URL link : processPage(url)) {
if (Thread.currentThread().isInterrupted())
return;
submitCrawlTask(link);
}
}
public URL getPage() {
return url;
}
}
}
But I don't understand what is the exact chronology of calls of runnable.run(), exec.shutdownNow(), exec.awaitTermination(...), exec.getCancelledTasks(), tasksCancelledAtShutdown.add(runnable), the runnable completion and thread-interleaving, which leads to a race condition.
This is how I understand it. For example,TrackingExecutor is shutting down before CrawlTask exit, this task may be also recorded as a taskCancelledAtShutdown, because if (isShutdown() && Thread.currentThread().isInterrupted()) in TrackingExecutor#execute may be true , but in fact this task has completed.
private class CrawlTask implements Runnable {
public void run() {
for (URL link : processPage(url)) {
if (Thread.currentThread().isInterrupted())
return;
submitCrawlTask(link);
}
// May be here, trackingExecutor is shutting down.
// Actually this task has completed now.But this method did not exit.
}
}
public void execute(final Runnable runnable) {
exec.execute(new Runnable() {
public void run() {
try {
runnable.run();
} finally {
// isShutdown() && Thread.currentThread().isInterrupted() may be true
if (isShutdown()
&& Thread.currentThread().isInterrupted())
tasksCancelledAtShutdown.add(runnable);
}
}
});
}
I'm trying to write something using reactor which I know how to write using completable futures. I'm getting "Calling subscribe in non-blocking scope" warning in it.
My goal is to call turnOn() with a timeout which should call turnOff() after the timeout. If turnOn() is called again it should cancel the old timeout and wait for a new timeout.
How should I do this? I could do a hibrate and use CompletableFuture for the timeout but reactor's api is just a bit easier.
this test works as expected:
public class TimeoutTest {
Service service;
#BeforeEach
public void setUp() {
service = mock(Service.class);
}
CompletableFuture<Void> turnOffFuture = null;
#DisplayName("Should timeout on turnOn with timeout")
#Test
public void timeoutCompletableFuture() throws InterruptedException {
turnOn(Duration.ofMillis(100)).join();
verify(service).turnOn();
verify(service,never()).turnOff();
Thread.sleep(1000);
verify(service).turnOff();
}
private interface Service{
void turnOn();
void turnOff();
}
public void cancelTimeout() {
if (turnOffFuture != null)
turnOffFuture.cancel(false);
turnOffFuture = null;
}
public CompletableFuture<Void> turnOn(Duration timeout) {
CompletableFuture<Void> turnOnFuture = turnOn();
cancelTimeout();
turnOffFuture = turnOnFuture.thenRun(() -> delay(timeout))
.thenRun(this::turnOff);
return turnOnFuture;
}
private void delay(Duration duration) {
try {
Thread.sleep(BigDecimal.valueOf(duration.getSeconds())
.scaleByPowerOfTen(3)
.add(BigDecimal.valueOf(duration.getNano(), 6))
.intValue());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private CompletableFuture<Void> turnOn() {
return CompletableFuture.runAsync(() -> service.turnOn());
}
private CompletableFuture<Void> turnOff() {
return CompletableFuture.runAsync(() -> service.turnOff());
}
}
but my reactor code does not.
public class TimeoutMonoTest {
Service service;
#BeforeEach
public void setUp() {
service = mock(Service.class);
}
Disposable turnOffDisposable = null;
#DisplayName("Should timeout on turnOn with timeout")
#Test
public void timeoutMono() throws InterruptedException {
turnOn(Duration.ofMillis(100)).block(Duration.ofMillis(10));
verify(service).turnOn();
verify(service, never()).turnOff();
Thread.sleep(1000);
verify(service).turnOff();
}
private interface Service {
void turnOn();
void turnOff();
}
public void cancelTimeout() {
if (turnOffDisposable != null)
turnOffDisposable.dispose();
turnOffDisposable = null;
}
public Mono<Void> turnOn(Duration timeout) {
Mono<Void> turnOnFuture = turnOn();
cancelTimeout();
turnOffDisposable = turnOnFuture.delayElement(timeout)
.subscribe(it -> this.turnOff());
return turnOnFuture;
}
private Mono<Void> turnOn() {
service.turnOn();
return Mono.just("not empty but mapped to void").then();
}
private Mono<Void> turnOff() {
service.turnOff();
return Mono.just("not empty but mapped to void").then();
}
}
The problem lies in the mapping to void mono's in the turnOn() and turnOff() methods. They do not actually get a "next" signal, just a "success" signal.
The fix is simply to change the turnOn method to:
public Mono<Void> turnOn(Duration timeout) {
cancelTimeout();
Mono<Void> turnOnMono = turnOn();
turnOffDisposable = turnOnMono.delayElement(timeout)
.then(turnOff())
.subscribe();
return turnOn();
}
This question is an architectural problem that I have not been able to figure out.
I have a TaskScheduler that has operations such as start() and stop(). TaskScheduler is intended to be agnostic, I want to be able to pass into it any "Runnable", a "UID" and the "Interval" that the service should run for. This all gets added to a hashmap so that if you try to pass in an existing runnable with the same UID it will replace the previous runnable with the new information.
Extending the TaskScheduler is MyScheduler, which is specific to the request that I want to make. In this example, I am making multiple Profile requests every 60 seconds. To keep track of which profile request is which, I am using UID as a key.
I want to then bubble up the responses to the app level from MyScheduler. This is where I am having issues. I am only able to bubble up the response from the latest scheduler. So if I create Scheduler A and Scheduler B, I will receive updates only from Scheduler B. Simlarly, if I create Scheduler A-C, then I will receive updates only from Scheduler C.
I know why this is, MyScheduler uses the last request that was passed into it. However, I do not know a good pattern (methodology) to resolve this.
TaskScheduler class
public class TaskScheduler {
private static Map<String, SchedulerModel> schedulerModels = new HashMap<>();
TaskScheduler() {}
private ScheduledFuture<?> start(#NotNull final SchedulerModel schedulerModel) {
return schedulerModel.executorService.scheduleWithFixedDelay(schedulerModel.runnable, 0, schedulerModel.interval, TimeUnit.SECONDS);
}
/**
* Method is used to onSchedulerStop executing tasks
*/
private void shutdown(#NotNull SchedulerModel schedulerModel) {
if (schedulerModel.executorService != null) {
schedulerModel.executorService.shutdownNow();
schedulerModel.executorService = null;
}
}
/**
* Method is used to initialize scheduler task and time delays
*
* #param runnable Represents a command that can be executed
* #param interval The time interval for execution of code
*/
void setTask(Runnable runnable, String uid, int interval) {
SchedulerModel schedulerModel = new SchedulerModel();
schedulerModel.executorService = Executors.newSingleThreadScheduledExecutor();
schedulerModel.runnable = runnable;
schedulerModel.interval = interval;
schedulerModels.put(uid, schedulerModel);
}
public void stop(#NotNull String uid) {
if (schedulerModels.get(uid) != null) {
shutdown(schedulerModels.get(uid));
schedulerModels.remove(uid);
} else {
// scheduler id not found
}
}
public void start(#NotNull String uid) {
if (schedulerModels.get(uid) != null) {
start(schedulerModels.get(uid));
} else {
// scheduler id not found
}
}
}
MyScheduler (this name is temporary)
public class MyScheduler extends TaskScheduler {
private final int DEFAULT_SCHEDULER_INTERVAL = 60; // seconds
private ProfileRequest request;
private ApiInterface apiInterface;
private SchedulerInterface schedulerInterface;
public MyScheduler() {}
public void createScheduler(#NotNull ApiInterface apiInterface,
#NotNull ProfileRequest request,
#NotNull SchedulerInterface schedulerInterface) {
this.apiInterface = apiInterface;
this.request = request;
this.schedulerInterface = schedulerInterface;
super.setTask(new SchedulerRunnable(), request.getUid(), DEFAULT_SCHEDULER_INTERVAL);
}
public void start(#NotNull String uid) {
start(uid); // start scheduler
schedulerInterface.onSchedulerStart(uid); // send feedback to callback
}
public void stop(#NotNull String uid) {
stop(uid); // stop scheduler
schedulerInterface.onSchedulerStop(uid); // send feedback to callback
}
private class SchedulerRunnable implements Runnable {
#Override
public void run() {
ApiClient.createBookmark(request, new Callback<Response>() {
#Override
public void onSuccess(#NotNull Response response) {
schedulerInterface.onSuccess(response);
}
#Override
public void onFailure(#NotNull Exception exception) {
schedulerInterface.onFailure(exception);
}
});
}
}
}
Trying to achieve this on app level
mProfileScheduler.createScheduler(apiInterface, request, new SchedulerInterface {
Override
public void onSuccess(Response response) {
// problem so far is that I only get response from latest scheduler
}
Override
public void onFailure(Exception exception) {}
Override
public void onSchedulerStop(String uid) {
// pass back uid so that I know which profile scheduler was stopped
}
Override
public void onSchedulerStart(String uid) {}
// pass back uid so that I know which profile scheduler was started
}
});
You have this problem because schedulerInterface is a member of MyScheduler.
Thus it's shared across all tasks and is overwritten after each new task is submitted.
Solution here is to make schedulerInterface a member of SchedulerRunnable:
private class SchedulerRunnable implements Runnable {
private SchedulerInterface schedulerInterface;
SchedulerRunnable(SchedulerInterface schedulerInterface) {
this.schedulerInterface = schedulerInterface;
}
}
In order to invoke onSchedulerStop() and onSchedulerStart() you can make start() and stop in TaskScheduler return Runnable. Then in MyTaskScheduler you would cast it to SchedulerRunnable to obtain a reference to the schedulerInterface.
If you don't want Runnable to be returned as part of public interface, you can create protected methods e.g. Runnable doStart() and Runnable doStop that can be overridden and are invoked by void start() and void stop().
Other issues
Concurrency
You are using HashMap for TaskScheduler schedulerModels. It is not thread-safe.
This is OK if you do not intend to access it from more than one thread.
Otherwise, you may encounter issues with race conditions and memory visibility.
You should use ConcurrentHashMap and its atomic operations like computeIfPresent() or computeIfAbsent() instead of put.
Resource management
When you replace an existing task with a new one with same UID you neither stop its executor service nor cancel the currently running task. Thus you are going to leak threads and previous runnable will keep running.
You create a new SingleThreadExecutorService for each task. That makes number of used threads potentially unbounded and makes it's hard to make any guarantees about application resource consumption. Normally you would use a thread pool with fixed number of threads that are reused between tasks.
Again, I suggest reading "Java Concurrency in Practice" book to learn about these problems and patterns to solve them.
Full Solution
After talking in chat this is my suggested solution.
I've mocked all unspecified classes and interfaces.
import org.jetbrains.annotations.NotNull;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class Main {
private static class MySchedulerInterface implements SchedulerInterface {
private final ProfileRequest request;
public MySchedulerInterface(ProfileRequest req1) {
this.request = req1;
}
#Override
public void onSuccess(String response) {
System.out.println("onSuccess:[" + request + "]" + response);
}
#Override
public void onFailure(Exception exception) {
System.out.println("onFailure:[" + request + "]" + exception);
}
#Override
public void onSchedulerStop(String uid) {
System.out.println("onSchedulerStop:[" + request + "] - " + uid);
}
#Override
public void onSchedulerStart(String uid) {
System.out.println("onSchedulerStart:[" + request + "] - " + uid);
}
}
public static void main(String[] args) throws InterruptedException {
ApiInterface api = new ApiInterface();
ProfileRequest req1 = new ProfileRequest("1", "apple");
ProfileRequest req2 = new ProfileRequest("2", "orange");
ProfileRequest req3 = new ProfileRequest("3", "peach");
ProfileRequest req11 = new ProfileRequest("1", "pineapple");
MyScheduler scheduler = new MyScheduler();
scheduler.createScheduler(api, req1, new MySchedulerInterface(req1));
scheduler.createScheduler(api, req2, new MySchedulerInterface(req2));
scheduler.createScheduler(api, req3, new MySchedulerInterface(req3));
System.out.println("Created 3 tasks");
TimeUnit.SECONDS.sleep(2);
System.out.println("Starting 3 tasks");
scheduler.start("1");
scheduler.start("2");
scheduler.start("3");
System.out.println("Started 3 tasks");
TimeUnit.SECONDS.sleep(10);
System.out.println("Replacing task 1...");
scheduler.createScheduler(api, req11, new MySchedulerInterface(req11));
System.out.println("Replaced task 1.");
TimeUnit.SECONDS.sleep(10);
System.out.println("Stopping 3 tasks...");
scheduler.stop("1");
scheduler.stop("2");
scheduler.stop("3");
System.out.println("The end.");
}
}
class ProfileRequest {
private final String uid;
private final String value;
public ProfileRequest(String uid, String value) {
this.uid = uid;
this.value = value;
}
public String getUid() {
return uid;
}
public String getValue() {
return value;
}
#Override
public String toString() {
return new StringJoiner(", ", ProfileRequest.class.getSimpleName() + "[", "]")
.add("uid='" + uid + "'")
.add("value='" + value + "'")
.toString();
}
}
class ApiInterface {
public void createBookmark(ProfileRequest request, Callback<String> stringCallback) {
stringCallback.onSuccess("SUCCESS: I'm done with " + request);
}
}
interface SchedulerInterface {
void onSuccess(String response);
void onFailure(Exception exception);
void onSchedulerStop(String uid);
void onSchedulerStart(String uid);
}
interface Callback<T> {
void onSuccess(#NotNull T response);
void onFailure(#NotNull Exception exception);
}
class MyScheduler extends TaskScheduler {
private final int DEFAULT_SCHEDULER_INTERVAL = 2; // seconds
public MyScheduler() {
}
public void createScheduler(#NotNull ApiInterface apiInterface,
#NotNull ProfileRequest request,
#NotNull SchedulerInterface schedulerInterface) {
super.setTask(new SchedulerRunnable(apiInterface, request, schedulerInterface), request.getUid(), DEFAULT_SCHEDULER_INTERVAL);
}
#Override
public ScheduledTask doStart(#NotNull String uid) {
final ScheduledTask task = super.doStart(uid);
if (task != null) {
final SchedulerRunnable runnable = (SchedulerRunnable) task.runnable;
runnable.schedulerInterface.onSchedulerStart(uid);
}
return task;
}
#Override
protected ScheduledTask doStop(#NotNull String uid) {
final ScheduledTask task = super.doStop(uid);
if (task != null) {
final SchedulerRunnable runnable = (SchedulerRunnable) task.runnable;
runnable.schedulerInterface.onSchedulerStop(uid);
}
return task;
}
private class SchedulerRunnable implements Runnable {
private final ApiInterface apiInterface;
private final ProfileRequest request;
private final SchedulerInterface schedulerInterface;
SchedulerRunnable(ApiInterface apiInterface, ProfileRequest request, SchedulerInterface schedulerInterface) {
this.apiInterface = apiInterface;
this.request = request;
this.schedulerInterface = schedulerInterface;
}
#Override
public void run() {
apiInterface.createBookmark(request, new Callback<String>() {
#Override
public void onSuccess(#NotNull String response) {
schedulerInterface.onSuccess(response);
}
#Override
public void onFailure(#NotNull Exception exception) {
schedulerInterface.onFailure(exception);
}
});
}
}
}
class SchedulerModel {
ScheduledExecutorService executorService;
Runnable runnable;
int interval;
}
class TaskScheduler {
static class ScheduledTask {
String uid;
Runnable runnable;
int interval;
ScheduledFuture<?> future;
ScheduledTask(String uid, Runnable runnable, int interval, ScheduledFuture<?> future) {
this.uid = uid;
this.runnable = runnable;
this.interval = interval;
this.future = future;
}
void dispose() {
if (future != null) {
future.cancel(true);
}
}
boolean isScheduled() {
return future != null;
}
}
private ConcurrentMap<String, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>();
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
TaskScheduler() {
}
/**
* Method is used to initialize scheduler task and time delays
*
* #param runnable Represents a command that can be executed
* #param interval The time interval for execution of code
*/
void setTask(Runnable runnable, String uid, int interval) {
AtomicBoolean requiresRestart = new AtomicBoolean(false);
final ScheduledTask task = scheduledTasks.compute(uid, (id, oldTask) -> {
ScheduledTask newTask = new ScheduledTask(uid, runnable, interval, null);
if (oldTask != null) {
oldTask.dispose();
requiresRestart.set(oldTask.isScheduled());
}
return newTask;
});
if (requiresRestart.get()) {
start(uid);
}
}
public void start(#NotNull String uid) {
doStart(uid);
}
public void stop(#NotNull String uid) {
doStop(uid);
}
protected ScheduledTask doStart(#NotNull String uid) {
final ScheduledTask scheduledTask = scheduledTasks.computeIfPresent(uid, (id, oldTask) -> {
ScheduledFuture<?> future = executor.scheduleWithFixedDelay(
oldTask.runnable, 0, oldTask.interval, TimeUnit.SECONDS);
ScheduledTask newTask = new ScheduledTask(oldTask.uid, oldTask.runnable, oldTask.interval, future);
return newTask;
});
return scheduledTask;
}
protected ScheduledTask doStop(#NotNull String uid) {
final ScheduledTask task = scheduledTasks.remove(uid);
task.dispose();
return task;
}
}
I have a bunch of worker threads that I want to execute sequentially after a giving delay. I would like to achieve the following behaviour:
DELAY -> Worker 1 -> DELAY -> Worker 2 - DELAY -> Worker 3 -> ...
I came up with this solution:
long delay = 5;
for(String value : values) {
WorkerThread workerThread = new WorkerThread(value);
executorService.schedule(workerThread, delay, TimeUnit.SECONDS);
delay = delay + 5;
}
where executorService has been created like:
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
Is there any other way to achieve this with ExecutorService in Java?
Looking at your problem, I came up with another solution. Assuming values is a queue which can be changed. Here is a solution which works. I modified your WorkerThread a little and added a callback object in there. Hope this helps.
private final Queue<String> values = new LinkedList<>();
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private void start() {
AtomicLong delay = new AtomicLong(5);
String value = values.poll();
if (value != null) {
WorkerThread workerThread = new WorkerThread(value, new OnCompleteCallback() {
#Override
public void complete() {
String valueToProcessNext = values.poll();
if (valueToProcessNext != null) {
executorService.schedule(new WorkerThread(valueToProcessNext, this), delay.addAndGet(5), TimeUnit.SECONDS);
}
}
});
executorService.schedule(workerThread, delay.get(), TimeUnit.SECONDS);
}
}
class WorkerThread implements Runnable {
private final String value;
private final OnCompleteCallback callback;
WorkerThread(String value, OnCompleteCallback callback) {
this.value = value;
this.callback = callback;
}
#Override
public void run() {
try {
System.out.println(value);
} finally {
callback.complete();
}
}
}
interface OnCompleteCallback {
void complete();
}
Nothing comes to mind except for your solution if ExecutorService should be used. However you will probably find CompletableFuture more useful cause it provides similar behavior but with delay relative to task completion instead for start of scheduling.
CompletableFuture<Void> completableFuture = CompletableFuture.completedFuture(null);
String[] values = new String[]{"a", "b", "c"};
for (String value : values) {
completableFuture
.thenRun(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
})
.thenRun(() -> System.out.println(value));
}
completableFuture.get();
You can use a DelayQueue between each worker. And decorate your workers with this class :
public class DelayedTask implements Runnable {
private final Runnable task;
private final DelayQueue<Delayed> waitQueue;
private final DelayQueue<Delayed> followerQueue;
public DelayedTask(Runnable task, DelayQueue<Delayed> waitQueue, DelayQueue<Delayed> followerQueue) {
this.task = Objects.requireNonNull(task);
this.waitQueue = Objects.requireNonNull(waitQueue);
this.followerQueue = followerQueue;
}
#Override
public void run() {
try {
waitQueue.take();
try {
task.run();
} finally {
if (followerQueue != null) {
followerQueue.add(new Delay(3, TimeUnit.SECONDS));
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
and a simple Delayed implementation
class Delay implements Delayed {
private final long nanos;
Delay(long amount, TimeUnit unit) {
this.nanos = TimeUnit.NANOSECONDS.convert(amount, unit) + System.nanoTime();
}
#Override
public long getDelay(TimeUnit unit) {
return unit.convert(nanos - System.nanoTime(), TimeUnit.NANOSECONDS);
}
#Override
public int compareTo(Delayed other) {
return Long.compare(nanos, other.getDelay(TimeUnit.NANOSECONDS));
}
}
Allowing for this usage :
ExecutorService executorService = Executors.newFixedThreadPool(1);
// ....
DelayQueue<Delayed> currentQueue = new DelayQueue<>();
currentQueue.add(new Delay(3, TimeUnit.SECONDS));
for (String value : values) {
DelayedTask delayedTask = new DelayedTask(new WorkerThread(value), currentQueue, currentQueue = new DelayQueue<>());
executorService.submit(delayedTask);
}
Precondition :
I implement chating in my application and try to implement schedule mechanism. Where user doesn't have internet connection it will be put into scheduled queue.
Problem explanation :
I have a single thread executor
private ExecutorService executor = Executors.newSingleThreadExecutor();
And have a Scheduler for my Tasks
public class MessageScheduler {
private ConcurrentLinkedQueue<MessageTask> queue;
public MessageScheduler() {
queue = new ConcurrentLinkedQueue<>();
}
public void schedule(MessageTask messageTask) {
queue.add(messageTask);
}
public ArrayList<MessageTask> getScheduled() {
return new ArrayList<>(queue);
}
public boolean isEmpty() {
return queue.isEmpty();
}
public MessageTask pop() {
return queue.peek();
}
public void removeHead() {
queue.poll();
}
}
MessageTask have complete listener :
public interface OnMessageCompleteListener {
public void onMessageSendingComplete(MessageTask task);
public void onMessageSendingError(MessageTask task);
}
In MessageTask i notify listener about task i complete :
#Override
public void run() {
// Async call
listener.onMessageSendingComplete(thisInstance);
}
The my problem is when i notify a listeners by onMessageSendingComplete() i need to execute scheduled messageTasks in same method :
#Override
public void onMessageSendingComplete(MessageTask task) {
scheduler.removeHead();
if(!scheduler.isEmpty()){
MessageTask readyToExecute = scheduler.pop();
// MessageTask may be not finished already.
// and executor was always will be busy or not?
execute(readyToExecute);
}
}
execute method :
public void execute(MessageTask messageTask) {
if (!executorIsBusy && isNetworkAvailable) {
messageTask.setOnCompleteListener(this);
executor.execute(messageTask);
executorIsBusy = true;
} else {
scheduler.schedule(messageTask);
}
}
Where i need set executorIsBusy to false that ExecutorService will be ready to execute?
Resolved.
After some thinking i find a solution.
I change
#Override
public void onMessageSendingComplete(MessageTask task) {
scheduler.removeHead();
if(!scheduler.isEmpty()){
MessageTask readyToExecute = scheduler.pop();
// MessageTask may be not finished already.
// and executor was always will be busy or not?
execute(readyToExecute);
}
}
to
#Override
public void onMessageSendingComplete(MessageTask task) {
scheduler.removeHead();
if(!scheduler.isEmpty()){
MessageTask readyToExecute = scheduler.pop();
readyToExecute.setOnCompleteListener(this);
executor.execute(readyToExecute);
} else {
executorIsBusy = false;
}
}