I have slightly refactored the Delayed, concurrent event stack in Java. Now it looks like this:
DelayedEventStack.java
package com.github.coderodde.cconcurrent.eventstack;
import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
/**
* This class implements an delayed event stack.
*
* @author Rodion "rodde" Efremov
* @version 1.61 (Jan 14, 2023)
* @since 1.6 (Jan 12, 2023)
*/
public final class DelayedEventStack implements AutoCloseable {
/**
* This static inner class implements the event stack entry.
*/
private static final class DelayedEventStackEntry {
private final Runnable onExpired;
private final long expirationMillis;
DelayedEventStackEntry(Runnable onExpired, long expirationMillis) {
this.onExpired =
Objects.requireNonNull(onExpired, "onExpired is null.");
this.expirationMillis = expirationMillis;
}
}
/**
* The event loop flag.
*/
private volatile boolean doRunFlag = 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 dischargeRemainingEventsOnClose;
/**
* The worker thread.
*/
private final Thread workerThread = new Thread() {
@Override
public void run() {
while (doRunFlag) {
// 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
|| System.currentTimeMillis() <
topmostEventStackEntry.expirationMillis) {
// Once here, nothing to do.
Utils.sleep(1L);
} else {
delayedEventStack.removeLast().onExpired.run();
}
}
}
};
public DelayedEventStack() {
this(true);
}
public DelayedEventStack(boolean dischargeRemainingEventsOnClose) {
this.dischargeRemainingEventsOnClose = dischargeRemainingEventsOnClose;
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() {
doRunFlag = false;
if (dischargeRemainingEventsOnClose) {
while (!delayedEventStack.isEmpty()) {
delayedEventStack.removeLast().onExpired.run();
}
}
}
}
Utils.java
package com.github.coderodde.cconcurrent.eventstack;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class is supposed to hold miscellaneous utility methods.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Jan 14, 2023)
* @since 1.6 (Jan 14, 2023)
*/
public final class Utils {
private static final Logger LOGGER =
Logger.getLogger(Utils.class.getName());
private Utils() {
}
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
LOGGER.log(Level.WARNING, "Interrupted while sleeping.");
}
}
}
Demo.java
package com.github.coderodde.cconcurrent.eventstack;
import static com.github.coderodde.cconcurrent.eventstack.Utils.sleep;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class Demo {
public static void main(String[] args) {
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();
}
}
/**
* This class is responsible for closing the event stack.
*/
class CloseThread extends Thread {
private static final Logger logger =
Logger.getLogger(CloseThread.class.getName());
private final DelayedEventStack delayedEventStack;
CloseThread(DelayedEventStack delayedEventStack) {
this.delayedEventStack = delayedEventStack;
}
@Override
public void run() {
Utils.sleep(10_000L);
try {
delayedEventStack.close();
} catch (Exception ex) {
logger.log(
Level.SEVERE,
"An exception was thrown upon closing the event stack: {0}",
ex.getMessage());
}
}
}
Critique request
Since I am not good at writing concurrent code, I need your, guys, help to make it mature.
DelayQueue. Should it execute evens in order, i.e. earlier events should be executed first even if later ones already expires? \$\endgroup\$