diff --git a/guarded-suspension/README.md b/guarded-suspension/README.md index 9dc39f2c2..39703c065 100644 --- a/guarded-suspension/README.md +++ b/guarded-suspension/README.md @@ -3,24 +3,27 @@ title: Guarded Suspension category: Concurrency language: en tag: - - Decoupling + - Asynchronous + - Decoupling + - Resource management + - Synchronization + - Thread management --- +## Also known as + +* Conditional Block +* Suspended Execution + ## Intent -Use Guarded suspension pattern to handle a situation when you want to execute a method on object which is not in a proper state. -## Class diagram -![Guarded Suspension diagram](./etc/guarded-suspension.png) - -## Applicability -Use Guarded Suspension pattern when the developer knows that the method execution will be blocked for a finite period of time +The Guarded Suspension pattern manages operations that require both a lock and a condition to proceed, allowing a thread to wait for an appropriate condition while being efficient with resource use. ## Explanation Real world example -> When we reserve a dining room online and arrive to find it unready, the manager has it cleaned while we wait. -> Once ready, we're escorted to the room. This process exemplifies the Guarded Suspension pattern. +> 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. In plain words @@ -28,24 +31,23 @@ In plain words Wikipedia says -> In concurrent programming, Guarded Suspension manages operations requiring a lock -> and a precondition, delaying execution until the precondition is met. +> In concurrent programming, Guarded Suspension manages operations requiring a lock and a precondition, delaying execution until the precondition is met. **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 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: ```java +@Slf4j public class GuardedQueue { -private final Queue sourceList = new LinkedList<>(); + private final Queue sourceList = new LinkedList<>(); public synchronized Integer get() { while (sourceList.isEmpty()) { try { wait(); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } return sourceList.peek(); @@ -56,11 +58,15 @@ private final Queue sourceList = new LinkedList<>(); notify(); } } +``` +Here is the `App` class driving the example. + +```java public class App { -public static void main(String[] args) { -GuardedQueue guardedQueue = new GuardedQueue(); -ExecutorService executorService = Executors.newFixedThreadPool(3); + public static void main(String[] args) { + 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); @@ -68,7 +74,7 @@ ExecutorService executorService = Executors.newFixedThreadPool(3); try { Thread.sleep(2000); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } // Here we create the second thread which is supposed to put to guardedQueue @@ -80,13 +86,54 @@ ExecutorService executorService = Executors.newFixedThreadPool(3); try { executorService.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } } ``` +Executing the example yields: -## Related patterns +``` +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 +``` -* Balking +## Class diagram + +![Guarded Suspension diagram](./etc/guarded-suspension.png) + +## Applicability + +This pattern is used in scenarios where a thread needs to wait for certain conditions to be met before it can proceed, ensuring that resources are utilized only when necessary and reducing the overhead of busy waiting. + +## Known Uses + +* Network servers waiting for client requests. +* Producer-consumer scenarios where the consumer must wait for the producer to provide data. +* Event-driven applications where actions are triggered only after specific events have occurred. + +## Consequences + +Benefits: + +* Reduces CPU consumption by preventing busy waiting. +* Increases system responsiveness by synchronizing actions to the availability of necessary conditions or resources. + +Trade-offs: + +* Complexity in implementation, especially when multiple conditions need to be managed. +* Potential for deadlocks if not carefully managed. + +## 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. + +## Credits + +* [Java Concurrency in Practice](https://amzn.to/3JxnXek) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/49Ke1c9) diff --git a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java index 289d28c15..972fa97b2 100644 --- a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java +++ b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java @@ -26,18 +26,20 @@ package com.iluwatar.guarded.suspension; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; /** - * Created by robertt240 on 1/26/17. - * *

Guarded-suspension is a concurrent design pattern for handling situation when to execute some * action we need condition to be satisfied. - * - *

Implementation is based on GuardedQueue, which has two methods: get and put, the condition is - * that we cannot get from empty queue so when thread attempt to break the condition we invoke - * Object's wait method on him and when other thread put an element to the queue he notify the - * waiting one that now he can get from queue. + * The implementation utilizes a GuardedQueue, which features two primary methods: `get` and `put`. + * The key condition governing these operations is that elements cannot be retrieved (`get`) from + * an empty queue. When a thread attempts to retrieve an element under this condition, it triggers + * the invocation of the `wait` method from the Object class, causing the thread to pause. + * Conversely, when an element is added (`put`) to the queue by another thread, it invokes the + * `notify` method. This notifies the waiting thread that it can now successfully retrieve an + * element from the queue. */ +@Slf4j public class App { /** * Example pattern execution. @@ -56,7 +58,7 @@ public class App { try { Thread.sleep(2000); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } // now we execute second thread which will put number to guardedQueue // and notify first thread that it could get @@ -65,8 +67,7 @@ public class App { try { executorService.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } - } diff --git a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java index 1238762bb..c53868b32 100644 --- a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java +++ b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java @@ -44,7 +44,7 @@ public class GuardedQueue { } /** - * Get the last element of the queue is exists. + * Get the last element of the queue if exists. * * @return last element of a queue if queue is not empty */ @@ -54,7 +54,7 @@ public class GuardedQueue { LOGGER.info("waiting"); wait(); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } } LOGGER.info("getting"); diff --git a/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java b/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java index 33e9fc6b7..3a75a29e4 100644 --- a/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java +++ b/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java @@ -28,11 +28,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; /** * Test for Guarded Queue */ +@Slf4j class GuardedQueueTest { private volatile Integer value; @@ -46,7 +48,7 @@ class GuardedQueueTest { try { executorService.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error occurred: ", e); } assertEquals(Integer.valueOf(10), value); }