From 9c796135b54d43950ff63634b029417561a0494c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sun, 26 May 2024 09:39:04 +0300 Subject: [PATCH] docs: update guarded suspension --- guarded-suspension/README.md | 45 ++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/guarded-suspension/README.md b/guarded-suspension/README.md index 39703c065..8a52bc3dc 100644 --- a/guarded-suspension/README.md +++ b/guarded-suspension/README.md @@ -21,9 +21,9 @@ The Guarded Suspension pattern manages operations that require both a lock and a ## Explanation -Real world example +Real-world example -> When we book a dining room online and arrive to find it not yet prepared, the manager arranges for it to be cleaned while we wait. Once the room is ready, we are then escorted to it. This scenario illustrates the Guarded Suspension pattern, where our access to the room is contingent upon a specific condition being met—namely, the room being cleaned. +> A real-world example of the Guarded Suspension pattern is a ride-sharing service where passengers wait for a car to be available. When a passenger requests a ride, the request is suspended until a driver becomes available. The system monitors the availability of drivers, and once a driver is ready to take a new passenger, the system notifies the waiting passenger and resumes the ride request process. This ensures that passengers are not continuously checking for available drivers and that drivers are efficiently matched with passengers based on their availability. In plain words @@ -35,13 +35,14 @@ Wikipedia says **Programmatic Example** -The `GuardedQueue` class encapsulates a queue, and provides two synchronized methods, `get` and `put`. The `get` method waits if the queue is empty, and the `put` method adds an item to the queue and notifies waiting threads: +The `GuardedQueue` class demonstrates the Guarded Suspension pattern by encapsulating a queue and providing two synchronized methods, `get` and `put`. The `get` method waits if the queue is empty, while the `put` method adds an item to the queue and notifies any waiting threads. ```java @Slf4j public class GuardedQueue { private final Queue sourceList = new LinkedList<>(); + // Synchronized get method waits until the queue is not empty public synchronized Integer get() { while (sourceList.isEmpty()) { try { @@ -50,9 +51,10 @@ public class GuardedQueue { LOGGER.error("Error occurred: ", e); } } - return sourceList.peek(); + return sourceList.poll(); } + // Synchronized put method adds an item to the queue and notifies waiting threads public synchronized void put(Integer e) { sourceList.add(e); notify(); @@ -60,7 +62,10 @@ public class GuardedQueue { } ``` -Here is the `App` class driving the example. +* `get`: This method waits while the `sourceList` is empty. When an item is added and `notify` is called, the waiting thread is awakened to continue execution and retrieve the item. +* `put`: This method adds an item to the queue and calls `notify` to wake up any waiting thread that is suspended in the `get` method. + +Here is the `App` class driving the example: ```java public class App { @@ -68,18 +73,23 @@ public class App { GuardedQueue guardedQueue = new GuardedQueue(); ExecutorService executorService = Executors.newFixedThreadPool(3); - // Here we create the first thread which is supposed to get from guardedQueue - executorService.execute(guardedQueue::get); + // Thread to get from the guardedQueue + executorService.execute(() -> { + Integer item = guardedQueue.get(); + LOGGER.info("Retrieved: " + item); + }); + // Simulating some delay before putting an item try { Thread.sleep(2000); } catch (InterruptedException e) { LOGGER.error("Error occurred: ", e); } - // Here we create the second thread which is supposed to put to guardedQueue + // Thread to put an item into the guardedQueue executorService.execute(() -> { guardedQueue.put(20); + LOGGER.info("Item added to queue"); }); executorService.shutdown(); @@ -92,18 +102,22 @@ public class App { } ``` -Executing the example yields: +* `ExecutorService` is used to manage a pool of threads. +* The first thread attempts to retrieve an item from the `GuardedQueue` and waits since the queue is initially empty. +* After a 2-second delay, the second thread adds an item to the queue, waking up the first thread. +* The first thread then retrieves the item and logs it. + +Execution yields: ``` 19:22:58.984 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- waiting 19:23:00.993 [pool-1-thread-2] INFO com.iluwatar.guarded.suspension.GuardedQueue -- putting 19:23:00.994 [pool-1-thread-2] INFO com.iluwatar.guarded.suspension.GuardedQueue -- notifying 19:23:00.994 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- getting +19:23:00.994 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- Retrieved: 20 ``` -## Class diagram - -![Guarded Suspension diagram](./etc/guarded-suspension.png) +* The log output shows the sequence of events: the first thread waits, the second thread puts an item, and the first thread then retrieves the item. This demonstrates the Guarded Suspension pattern in action. ## Applicability @@ -129,11 +143,12 @@ Trade-offs: ## Related Patterns -* Monitor Object: Both patterns manage the synchronization of threads based on conditions. Guarded Suspension specifically deals with suspending threads until conditions are met, while Monitor Object encapsulates condition and mutual exclusion handling. -* Producer-Consumer: Often implemented using Guarded Suspension to handle waiting consumers and producers efficiently. -* Balking: Similar to Guarded Suspension, Balking is used when a thread checks a condition and only proceeds if the condition is favorable; if not, it immediately returns or bails out. This pattern complements Guarded Suspension by managing actions based on immediate condition checks without waiting. +* [Monitor](https://java-design-patterns.com/patterns/monitor/): Both patterns manage the synchronization of threads based on conditions. Guarded Suspension specifically deals with suspending threads until conditions are met, while Monitor Object encapsulates condition and mutual exclusion handling. +* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Often implemented using Guarded Suspension to handle waiting consumers and producers efficiently. +* [Balking](https://java-design-patterns.com/patterns/balking/): Similar to Guarded Suspension, Balking is used when a thread checks a condition and only proceeds if the condition is favorable; if not, it immediately returns or bails out. This pattern complements Guarded Suspension by managing actions based on immediate condition checks without waiting. ## Credits +* [Concurrent Programming in Java : Design Principles and Patterns](https://amzn.to/4dIBqxL) * [Java Concurrency in Practice](https://amzn.to/3JxnXek) * [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/49Ke1c9)