diff --git a/event-asynchronous/README.md b/event-asynchronous/README.md deleted file mode 100644 index 0dbe37ed0..000000000 --- a/event-asynchronous/README.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Event-based Asynchronous -category: Concurrency -language: en -tag: - - Reactive ---- - -## Intent -The Event-based Asynchronous Pattern makes available the advantages of multithreaded applications while hiding many -of the complex issues inherent in multithreaded design. Using a class that supports this pattern can allow you to: - -1. Perform time-consuming tasks, such as downloads and database operations, "in the background," without interrupting your application. -2. Execute multiple operations simultaneously, receiving notifications when each completes. -3. Wait for resources to become available without stopping ("hanging") your application. -4. Communicate with pending asynchronous operations using the familiar events-and-delegates model. - -## Class diagram -![alt text](./etc/event-asynchronous.png "Event-based Asynchronous") - -## Applicability -Use the Event-based Asynchronous pattern(s) when - -* Time-consuming tasks are needed to run in the background without disrupting the current application. - -## Credits - -* [Event-based Asynchronous Pattern Overview](https://msdn.microsoft.com/en-us/library/wewwczdw%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) diff --git a/event-based-asynchronous/README.md b/event-based-asynchronous/README.md new file mode 100644 index 000000000..0c0a622d2 --- /dev/null +++ b/event-based-asynchronous/README.md @@ -0,0 +1,106 @@ +--- +title: Event-Based Asynchronous +category: Concurrency +language: en +tag: + - Asynchronous + - Decoupling + - Event-driven + - Fault tolerance + - Messaging + - Reactive + - Scalability +--- + +## Also known as + +* Asynchronous Event Handling + +## Intent + +The Event-Based Asynchronous pattern allows a system to handle tasks that might take some time to complete without blocking the execution of the program. It enables better resource utilization by freeing up a thread that would otherwise be blocked waiting for the task to complete. + +## Explanation + +Real-world example + +> A real-world analogy of the Event-Based Asynchronous design pattern is how a restaurant operates. When a customer places an order, the waiter records the order and passes it to the kitchen. Instead of waiting at the kitchen for the food to be prepared, the waiter continues to serve other tables. Once the kitchen completes the order, they signal (event) the waiter, who then delivers the food to the customer. This allows the waiter to handle multiple tasks efficiently without idle waiting, similar to how asynchronous programming handles tasks in parallel, enhancing overall efficiency and responsiveness. + +In Plain Words + +> The Event-Based Asynchronous design pattern allows tasks to be executed in the background, notifying the main program via events when completed, thereby enhancing system efficiency and responsiveness without blocking ongoing operations. + +**Programmatic Example** + +The Event-Based Asynchronous design pattern in this project is implemented using several key classes: + +* App: This is the main class that runs the application. It interacts with the EventManager to create, start, stop, and check the status of events. It can run in either interactive mode or non-interactive mode. +* EventManager: This class is the core of the Event-Based Asynchronous pattern implementation. It manages the lifecycle of events, including creating, starting, stopping, and checking the status of events. It maintains a map of event IDs to Event objects. +* Event: This is an abstract class that represents an event. It has two concrete subclasses: AsyncEvent and SyncEvent. An Event has an ID, a runtime (how long it should run), and a status (whether it's running, completed, or ready to start). It also has methods to start and stop the event. +* AsyncEvent: This is a subclass of Event that represents an asynchronous event. When an AsyncEvent is started, it runs in a separate thread without blocking the main thread. +* SyncEvent: This is a subclass of Event that represents a synchronous event. When a SyncEvent is started, it runs on the main thread and blocks it until the event is completed. +* MaxNumOfEventsAllowedException, LongRunningEventException, EventDoesNotExistException, InvalidOperationException: These are custom exceptions that are thrown by the EventManager when certain conditions are not met. For example, MaxNumOfEventsAllowedException is thrown when the maximum number of allowed events is exceeded. + +Here's a simplified code example of how these classes interact: + +```java +// Create an EventManager +EventManager eventManager=new EventManager(); + +// Create an asynchronous event that runs for 60 seconds +int asyncEventId=eventManager.createAsync(60); + +// Start the asynchronous event +eventManager.start(asyncEventId); + +// Check the status of the asynchronous event +eventManager.status(asyncEventId); + +// Stop the asynchronous event +eventManager.cancel(asyncEventId); +``` + +In this example, the App class creates an EventManager, then uses it to create, start, check the status of, and stop an asynchronous event. The EventManager creates an AsyncEvent object, starts it in a separate thread, checks its status, and stops it when requested. + +## Class diagram + +![alt text](./etc/event-asynchronous.png "Event-based Asynchronous") + +## Applicability + +* When multiple tasks can be processed in parallel and independently. +* Systems that require responsiveness and cannot afford to have threads blocked waiting for an operation to complete. +* In GUI applications where user interface responsiveness is critical. +* Distributed systems where long network operations are involved. + +## Known Uses + +* GUI libraries in Java (e.g., JavaFX, Swing with SwingWorker). +* Java Message Service (JMS) for handling asynchronous messaging. +* Java’s CompletableFuture and various Event-Driven Frameworks. + +## Consequences + +Benefits: + +* Improves application scalability and responsiveness. +* Reduces the resources wasted on threads that would simply wait for I/O operations. +* Enhances fault tolerance through isolation of process execution. + +Trade-offs: + +* Increases complexity of error handling as errors may occur in different threads or at different times. +* Can lead to harder-to-follow code and debugging challenges due to the non-linear nature of asynchronous code execution. + +Related Patterns + +* [Observer](https://java-design-patterns.com/patterns/observer/): Often used in conjunction where the observer reacts to events as they occur. +* Publish/Subscribe: Related in terms of event handling mechanisms, particularly for messaging and event distribution across components. +* [Command](https://java-design-patterns.com/patterns/command/): Useful for encapsulating all information needed to perform an action or trigger an event. + +## Credits + +* [Event-based Asynchronous Pattern Overview](https://msdn.microsoft.com/en-us/library/wewwczdw%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) +* [Java Concurrency in Practice](https://amzn.to/4cYY4kU) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3Uh7rW1) +* [Pro JavaFX 8: A Definitive Guide to Building Desktop, Mobile, and Embedded Java Clients](https://amzn.to/3vHUqLL) diff --git a/event-asynchronous/etc/event-asynchronous.png b/event-based-asynchronous/etc/event-asynchronous.png similarity index 100% rename from event-asynchronous/etc/event-asynchronous.png rename to event-based-asynchronous/etc/event-asynchronous.png diff --git a/event-asynchronous/etc/event-asynchronous.ucls b/event-based-asynchronous/etc/event-asynchronous.ucls similarity index 100% rename from event-asynchronous/etc/event-asynchronous.ucls rename to event-based-asynchronous/etc/event-asynchronous.ucls diff --git a/event-asynchronous/etc/event-asynchronous.urm.puml b/event-based-asynchronous/etc/event-asynchronous.urm.puml similarity index 100% rename from event-asynchronous/etc/event-asynchronous.urm.puml rename to event-based-asynchronous/etc/event-asynchronous.urm.puml diff --git a/event-based-asynchronous/etc/event-based-asynchronous.urm.puml b/event-based-asynchronous/etc/event-based-asynchronous.urm.puml new file mode 100644 index 000000000..518155ab4 --- /dev/null +++ b/event-based-asynchronous/etc/event-based-asynchronous.urm.puml @@ -0,0 +1,70 @@ +@startuml +package com.iluwatar.event.asynchronous { + class App { + - LOGGER : Logger {static} + + PROP_FILE_NAME : String {static} + ~ interactiveMode : boolean + + App() + + main(args : String[]) {static} + - processOption1(eventManager : EventManager, s : Scanner) + - processOption2(eventManager : EventManager, s : Scanner) + - processOption3(eventManager : EventManager, s : Scanner) + + quickRun() + + run() + + runInteractiveMode() + + setUp() + } + class AsyncEvent { + - LOGGER : Logger {static} + - eventId : int + - eventListener : ThreadCompleteListener + - eventTime : int + - isComplete : AtomicBoolean + - synchronous : boolean + - thread : Thread + + AsyncEvent(eventId : int, eventTime : int, synchronous : boolean) + + addListener(listener : ThreadCompleteListener) + - completed() + + isSynchronous() : boolean + + removeListener() + + run() + + start() + + status() + + stop() + } + interface Event { + + start() {abstract} + + status() {abstract} + + stop() {abstract} + } + class EventManager { + - DOES_NOT_EXIST : String {static} + + MAX_EVENT_TIME : int {static} + + MAX_ID : int {static} + + MAX_RUNNING_EVENTS : int {static} + + MIN_ID : int {static} + - currentlyRunningSyncEvent : int + - eventPool : Map + - rand : SecureRandom + + EventManager() + + cancel(eventId : int) + + completedEventHandler(eventId : int) + + create(eventTime : int) : int + + createAsync(eventTime : int) : int + - createEvent(eventTime : int, isSynchronous : boolean) : int + - generateId() : int + + getEventPool() : Map + + numOfCurrentlyRunningSyncEvent() : int + + shutdown() + + start(eventId : int) + + status(eventId : int) + + statusOfAllEvents() + } + interface ThreadCompleteListener { + + completedEventHandler(int) {abstract} + } +} +AsyncEvent --> "-eventListener" ThreadCompleteListener +AsyncEvent ..|> Event +EventManager ..|> ThreadCompleteListener +@enduml \ No newline at end of file diff --git a/event-asynchronous/pom.xml b/event-based-asynchronous/pom.xml similarity index 97% rename from event-asynchronous/pom.xml rename to event-based-asynchronous/pom.xml index 1acd158ba..22471b72b 100644 --- a/event-asynchronous/pom.xml +++ b/event-based-asynchronous/pom.xml @@ -32,7 +32,7 @@ java-design-patterns 1.26.0-SNAPSHOT - event-asynchronous + event-based-asynchronous org.junit.jupiter diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java similarity index 100% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java similarity index 88% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java index 63239a476..10177c6e2 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java @@ -24,6 +24,8 @@ */ package com.iluwatar.event.asynchronous; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,7 +42,7 @@ public class AsyncEvent implements Event, Runnable { @Getter private final boolean synchronous; private Thread thread; - private boolean isComplete = false; + private AtomicBoolean isComplete = new AtomicBoolean(false); private ThreadCompleteListener eventListener; @Override @@ -59,7 +61,7 @@ public class AsyncEvent implements Event, Runnable { @Override public void status() { - if (!isComplete) { + if (isComplete.get()) { LOGGER.info("[{}] is not done.", eventId); } else { LOGGER.info("[{}] is done.", eventId); @@ -68,17 +70,19 @@ public class AsyncEvent implements Event, Runnable { @Override public void run() { + var currentTime = System.currentTimeMillis(); - var endTime = currentTime + (eventTime * 1000); + var endTime = currentTime + (eventTime * 1000L); while (System.currentTimeMillis() < endTime) { try { - Thread.sleep(1000); // Sleep for 1 second. + TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { + LOGGER.error("Thread was interrupted: ", e); Thread.currentThread().interrupt(); return; } } - isComplete = true; + isComplete.set(true); completed(); } @@ -95,5 +99,4 @@ public class AsyncEvent implements Event, Runnable { eventListener.completedEventHandler(eventId); } } - } diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java similarity index 100% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java similarity index 94% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java index 6aea231ae..8069060fe 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java @@ -24,11 +24,14 @@ */ package com.iluwatar.event.asynchronous; +import java.io.Serial; + /** - * Custom Exception Class for Non Existent Event. + * Custom Exception Class for Non-Existent Event. */ public class EventDoesNotExistException extends Exception { + @Serial private static final long serialVersionUID = -3398463738273811509L; public EventDoesNotExistException(String message) { diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java similarity index 96% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java index 8414d145e..b6c773db2 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java @@ -27,6 +27,7 @@ package com.iluwatar.event.asynchronous; import java.security.SecureRandom; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import lombok.Getter; /** * EventManager handles and maintains a pool of event threads. {@link AsyncEvent} threads are created @@ -39,12 +40,14 @@ import java.util.concurrent.ConcurrentHashMap; public class EventManager implements ThreadCompleteListener { public static final int MAX_RUNNING_EVENTS = 1000; - // Just don't wanna have too many running events. :) + // Just don't want to have too many running events. :) public static final int MIN_ID = 1; public static final int MAX_ID = MAX_RUNNING_EVENTS; public static final int MAX_EVENT_TIME = 1800; // in seconds / 30 minutes. private int currentlyRunningSyncEvent = -1; private final SecureRandom rand; + + @Getter private final Map eventPool; private static final String DOES_NOT_EXIST = " does not exist."; @@ -66,7 +69,7 @@ public class EventManager implements ThreadCompleteListener { * @throws MaxNumOfEventsAllowedException When too many events are running at a time. * @throws InvalidOperationException No new synchronous events can be created when one is * already running. - * @throws LongRunningEventException Long running events are not allowed in the app. + * @throws LongRunningEventException Long-running events are not allowed in the app. */ public int create(int eventTime) throws MaxNumOfEventsAllowedException, InvalidOperationException, LongRunningEventException { @@ -87,7 +90,7 @@ public class EventManager implements ThreadCompleteListener { * @param eventTime Time an event should run for. * @return eventId * @throws MaxNumOfEventsAllowedException When too many events are running at a time. - * @throws LongRunningEventException Long running events are not allowed in the app. + * @throws LongRunningEventException Long-running events are not allowed in the app. */ public int createAsync(int eventTime) throws MaxNumOfEventsAllowedException, LongRunningEventException { @@ -206,13 +209,6 @@ public class EventManager implements ThreadCompleteListener { eventPool.remove(eventId); } - /** - * Getter method for event pool. - */ - public Map getEventPool() { - return eventPool; - } - /** * Get number of currently running Synchronous events. */ diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java similarity index 97% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java index 9b12e73fc..cffe3a3cc 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java @@ -24,11 +24,14 @@ */ package com.iluwatar.event.asynchronous; +import java.io.Serial; + /** * Type of Exception raised when the Operation being invoked is Invalid. */ public class InvalidOperationException extends Exception { + @Serial private static final long serialVersionUID = -6191545255213410803L; public InvalidOperationException(String message) { diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java similarity index 97% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java index 569ccc431..54e2717c8 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java @@ -24,11 +24,14 @@ */ package com.iluwatar.event.asynchronous; +import java.io.Serial; + /** * Type of Exception raised when the Operation being invoked is Long Running. */ public class LongRunningEventException extends Exception { + @Serial private static final long serialVersionUID = -483423544320148809L; public LongRunningEventException(String message) { diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java similarity index 97% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java index 243f02d73..af4da61f0 100644 --- a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java @@ -24,11 +24,14 @@ */ package com.iluwatar.event.asynchronous; +import java.io.Serial; + /** * Type of Exception raised when the max number of allowed events is exceeded. */ public class MaxNumOfEventsAllowedException extends Exception { + @Serial private static final long serialVersionUID = -8430876973516292695L; public MaxNumOfEventsAllowedException(String message) { diff --git a/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java similarity index 100% rename from event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java rename to event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java diff --git a/event-asynchronous/src/main/resources/config.properties b/event-based-asynchronous/src/main/resources/config.properties similarity index 100% rename from event-asynchronous/src/main/resources/config.properties rename to event-based-asynchronous/src/main/resources/config.properties diff --git a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java similarity index 99% rename from event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java rename to event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java index 8f1b40b4c..4e4e33ac6 100644 --- a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java +++ b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java @@ -35,7 +35,6 @@ class AppTest { /** * Issue: Add at least one assertion to this test case. - * * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])} * throws an exception. */ diff --git a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java similarity index 90% rename from event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java rename to event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java index e54a0874f..a6a5eb0c6 100644 --- a/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java +++ b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java @@ -100,7 +100,16 @@ class EventAsynchronousTest { var currentTime = System.currentTimeMillis(); // +2 to give a bit of buffer time for event to complete properly. var endTime = currentTime + (eventTime + 2 * 1000); - while (System.currentTimeMillis() < endTime) ; + + long sleepTime = endTime - System.currentTimeMillis(); + if (sleepTime > 0) { + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: ", e); + Thread.currentThread().interrupt(); + } + } assertTrue(eventManager.getEventPool().isEmpty()); @@ -128,7 +137,16 @@ class EventAsynchronousTest { var currentTime = System.currentTimeMillis(); // +2 to give a bit of buffer time for event to complete properly. var endTime = currentTime + (eventTime + 2 * 1000); - while (System.currentTimeMillis() < endTime) ; + + long sleepTime = endTime - System.currentTimeMillis(); + if (sleepTime > 0) { + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: ", e); + Thread.currentThread().interrupt(); + } + } assertTrue(eventManager.getEventPool().isEmpty()); diff --git a/pom.xml b/pom.xml index cb3e17443..b6bf99140 100644 --- a/pom.xml +++ b/pom.xml @@ -140,7 +140,7 @@ promise page-controller page-object - event-asynchronous + event-based-asynchronous event-queue queue-load-leveling object-mother