(See the next iteration:
Delayed, concurrent event stack in Java - follow-up )
Motivation
I was confronted with a task of having "message events" for a GUI program. The use case is as follows: we have two events; A and B. A is visible for, say, 10 seconds; and B is visible for 5 seconds. Also, suppose that B triggers after 2 seconds after the A triggers. All in all, we have these steps:
Atriggers at time 0 seconds,Btriggers at time 2 seconds,Bfinishes at time 7 seconds,Afinishes at time 10 seconds.
com.github.coderodde.eventstack.DelayedEventStack.java:
package com.github.coderodde.eventstack;
import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class implements an delayed event stack.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Jan 12, 2023)
* @since 1.6 (Jan 12, 2023)
*/
public final class DelayedEventStack implements AutoCloseable {
/**
* This static inner class implements the event stack entry.
*/
public static final class DelayedEventStackEntry {
private final Runnable onExpired;
private final long expirationMillis;
public DelayedEventStackEntry(Runnable onExpired,
long entryMillis,
long durationMillis) {
this.onExpired =
Objects.requireNonNull(onExpired, "onExpired is null.");
this.expirationMillis = entryMillis + durationMillis;
}
}
/**
* The atomic boolean flag for running the event loop.
*/
private final AtomicBoolean doRunFlag = new AtomicBoolean(true);
/**
* The actual event stack.
*/
private final Deque<DelayedEventStackEntry> delayedEventStack =
new ConcurrentLinkedDeque<>();
/**
* This flag specifies whether the stack runs the leftover events in the
* stack upon closing the stack.
*/
private final boolean runExpiredOnClose;
/**
* The worker thread.
*/
private final Thread workerThread = new Thread() {
@Override
public void run() {
while (doRunFlag.get()) {
// Ask delayedEventStack for a topmost event entry. Gets null if
// the stack is empty, in which case we sleep a millisecond and
// ask one more time.
DelayedEventStackEntry topmostEventStackEntry =
delayedEventStack.peekLast();
if (topmostEventStackEntry == null) {
// Once here, the stack is empty.
DelayedEventStack.sleep(1L);
} else if (System.currentTimeMillis() >=
topmostEventStackEntry.expirationMillis) {
// Once here, we can discharge an event from the stack.
topmostEventStackEntry.onExpired.run();
delayedEventStack.removeLast();
} else {
// Don't abuse the CPU.
DelayedEventStack.sleep(1L);
}
}
}
};
public DelayedEventStack() {
this(true);
}
public DelayedEventStack(boolean runExpiredOnClose) {
this.runExpiredOnClose = runExpiredOnClose;
workerThread.start();
}
public void add(Runnable onAdd, Runnable onExpired, long durationMillis) {
delayedEventStack.addLast(new DelayedEventStackEntry(
onExpired,
System.currentTimeMillis(),
durationMillis));
onAdd.run();
}
@Override
public void close() throws Exception {
while (!delayedEventStack.isEmpty()) {
delayedEventStack.removeLast().onExpired.run();
}
doRunFlag.set(false);
}
public static void main(String[] args) throws Throwable {
System.out.println("Simulation started.");
DelayedEventStack eventStack = new DelayedEventStack();
eventStack.add(() -> { System.out.println("Event 1 start"); },
() -> { System.out.println("Event 1 end");},
10_000L);
sleep(2_000L);
eventStack.add(() -> { System.out.println("Event 2 start"); },
() -> { System.out.println("Event 2 end");},
3000L);
sleep(7_000L);
eventStack.add(() -> { System.out.println("Leftover event start"); },
() -> { System.out.println("Leftover event end"); },
10_000L);
CloseThread closeThread = new CloseThread(eventStack);
closeThread.start();
}
static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {}
}
private static final class CloseThread extends Thread {
private final DelayedEventStack eventStack;
CloseThread(DelayedEventStack eventStack) {
this.eventStack = eventStack;
}
@Override
public void run() {
DelayedEventStack.sleep(10_000L);
try {
eventStack.close();
System.out.println("Event stack closed.");
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}
}
}
Output
Simulation started.
Event 1 start
Event 2 start
Event 2 end
Leftover event start
Leftover event end
Event 1 end
Event stack closed.
Critique request
As I am not proficient in concurrent computing, I need your help to make my event stack mature.