diff --git a/Implicit-lock-pattern/.gitignore b/Implicit-lock-pattern/.gitignore new file mode 100644 index 000000000000..b83d22266ac8 --- /dev/null +++ b/Implicit-lock-pattern/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/Implicit-lock-pattern/README.md b/Implicit-lock-pattern/README.md new file mode 100644 index 000000000000..6eb10c86d3c3 --- /dev/null +++ b/Implicit-lock-pattern/README.md @@ -0,0 +1,202 @@ +--- +title: "Implicit Lock Pattern in Java: Simplifying Concurrent Resource Access" +shortTitle: Implicit Lock +description: "Master the Implicit Lock pattern in Java to handle resource locking efficiently and safely. Learn how this design pattern promotes safe access to shared resources without direct lock management." +category: Creational +language: en +tag: + - Concurrency + - Synchronization + - Lock Management + - Multi-threading + - Resource Sharing +--- + +## Also known as + +* Implicit Locking + +## Intent of Implicit Lock Pattern + +The Implicit Lock pattern in Java is designed to simplify the management of resource locking in multi-threaded environments. It provides an abstraction layer where locks are automatically handled when resources are accessed, allowing developers to focus on business logic without worrying about manual lock management. + +## Detailed Explanation of Implicit Lock Pattern with Real-World Examples + +### Real-world example + +> Imagine a banking system where multiple users are attempting to access and modify their accounts at the same time. To avoid conflicting changes, the Implicit Lock pattern ensures that when a user accesses their account, the system automatically acquires a lock to prevent others from modifying the account simultaneously. Once the transaction is complete, the lock is released, allowing others to access the account. + +### In plain words + +> The Implicit Lock pattern automatically acquires and releases locks when resources are accessed, reducing the need for developers to manually manage locking. + +### Wikipedia says + +> The Implicit Lock pattern helps encapsulate the locking mechanisms and ensures that resources are accessed safely without manual intervention. It hides the complexity of lock management from the client code. + +## Programmatic Example of Implicit Lock in Java + +In this example, we simulate the access and modification of shared resources (e.g., a bank account) where the lock is implicitly managed. + +### Resource Class + +```java +public class Resource { + private String id; + + public Resource(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} +``` +The Resource class represents a shared resource in the system that can be locked and unlocked. It contains an id to uniquely identify the resource. This class is simple and serves as the basis for any resource that might require implicit locking in the system. + +```java +// LockManager Class +public class LockManager { + private final Map locks = new HashMap<>(); + + public boolean acquireLock(Resource resource) { + synchronized (this) { + if (!locks.containsKey(resource.getId())) { + locks.put(resource.getId(), new ReentrantLock()); + } + return locks.get(resource.getId()).tryLock(); + } + } + + public boolean releaseLock(Resource resource) { + synchronized (this) { + Lock lock = locks.get(resource.getId()); + if (lock != null && lock.isHeldByCurrentThread()) { + lock.unlock(); + locks.remove(resource.getId()); + return true; + } + return false; + } + } +} + +``` +The LockManager class is responsible for managing locks for resources. It maintains a map of resources to their corresponding locks. The acquireLock method tries to acquire a lock for a given resource. If no lock exists, it creates one. The releaseLock method releases the lock for a resource if it's held by the current thread. +```java +// Framework Class (Managing the Implicit Lock) +public class Framework { + private final LockManager lockManager; + + public Framework(LockManager lockManager) { + this.lockManager = lockManager; + } + + public boolean tryLockResource(Resource resource) { + return lockManager.acquireLock(resource); + } + + public boolean notifyReleaseLock(Resource resource) { + return lockManager.releaseLock(resource); + } + + public String loadCustomerData(Resource resource) { + return "Customer data for " + resource.getId(); + } +} + +``` +The Framework class manages the interaction between the client code and the LockManager. It provides methods to acquire and release locks implicitly. tryLockResource tries to acquire a lock for a resource, while notifyReleaseLock releases the lock. The loadCustomerData method simulates fetching customer data for the given resource. +```java +// BusinessTransaction Class (Client Using the Framework) +public class BusinessTransaction { + private final Framework framework; + + public BusinessTransaction(Framework framework) { + this.framework = framework; + } + + public void processTransaction(Resource resource) { + if (framework.tryLockResource(resource)) { + System.out.println("Processing transaction for " + resource.getId()); + // Simulate transaction logic + framework.notifyReleaseLock(resource); + } else { + System.out.println("Resource is locked. Try again later."); + } + } +} + +``` +The BusinessTransaction class represents the client code that interacts with the Framework to process transactions. It checks if a resource is available (not locked) and processes the transaction. After processing, it releases the lock. If the resource is already locked, it notifies the user to try again later + +```java +// Main Class (Simulation) +public class App { + public static void main(String[] args) { + Resource resource1 = new Resource("Account1"); + Resource resource2 = new Resource("Account2"); + + LockManager lockManager = new LockManager(); + Framework framework = new Framework(lockManager); + BusinessTransaction transaction = new BusinessTransaction(framework); + + transaction.processTransaction(resource1); // Successful + transaction.processTransaction(resource1); // Locked + transaction.processTransaction(resource2); // Successful + } +} + + +``` +The App class simulates the operation of the system. It creates resources (e.g., bank accounts), initializes the LockManager and Framework, and processes transactions through the BusinessTransaction class. It demonstrates how the implicit locking mechanism works by showing a successful transaction, a locked resource, and another successful transaction. + +This set of classes and their respective explanations illustrates how the Implicit Lock pattern is used to manage resource locking automatically in a multi-threaded environment, abstracting away the complexity of manual lock management. + +Class Diagram +![implicit-lock.png](etc%2Fimplicit-lock.png) + +When to Use the Implicit Lock Pattern in Java + +Use the Implicit Lock pattern in Java when: + + You need to handle concurrent access to shared resources safely. + You want to abstract the lock management to reduce boilerplate code and potential errors. + The system involves resources that must be locked and unlocked automatically without manual intervention. + You want to simplify your codebase by removing explicit lock handling. + You need to ensure that resources are accessed in a thread-safe manner, without introducing unnecessary complexity. + +Benefits and Trade-offs of Implicit Lock Pattern +Benefits: + + Simplicity: The lock management is abstracted away, so developers don't need to worry about handling locks explicitly. + Safety: Ensures thread-safe access to resources. + Flexibility: Allows resources to be automatically locked and unlocked when needed. + Maintainability: Reduces boilerplate code, making it easier to maintain and scale the system. + +Trade-offs: + + Overhead: Automatic locking and unlocking might introduce some performance overhead, especially with large numbers of resources. + Indirectness: The complexity of lock management is hidden, which can sometimes lead to challenges when debugging or understanding the exact locking behavior. + Limited Control: Since lock management is abstracted, developers may have less control over lock behavior in certain scenarios. + +Real-World Applications of Implicit Lock Pattern in Java + + Banking Systems: Managing access to user accounts to prevent conflicting updates during concurrent transactions. + E-commerce Platforms: Ensuring safe and consistent modification of inventory and order data when multiple users access the system simultaneously. + Database Systems: Implicit locks to ensure consistency when multiple transactions are accessing and modifying the database. + Distributed Systems: Managing resources in a distributed system where multiple nodes access the same data concurrently. + +Related Java Design Patterns + + Singleton: Singleton pattern often works with Implicit Lock to control global access to resources. + Factory Method: Factory Method can be used to generate instances of resources that require implicit locking. + Observer: Observer pattern can be combined with Implicit Lock to ensure thread-safe notifications. + +References and Credits + + Design Patterns: Elements of Reusable Object-Oriented Software + Design Patterns in Java + Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software + Java Design Patterns: A Hands-On Experience with Real-World Examples \ No newline at end of file diff --git a/Implicit-lock-pattern/etc/implicit-lock.png b/Implicit-lock-pattern/etc/implicit-lock.png new file mode 100644 index 000000000000..561636afd273 Binary files /dev/null and b/Implicit-lock-pattern/etc/implicit-lock.png differ diff --git a/Implicit-lock-pattern/etc/implicit-lock.puml b/Implicit-lock-pattern/etc/implicit-lock.puml new file mode 100644 index 000000000000..da9bef19a217 --- /dev/null +++ b/Implicit-lock-pattern/etc/implicit-lock.puml @@ -0,0 +1,39 @@ +@startuml +package com.iluwatar.implicitlockpattern { + class App { + + main(args: String[]) {static} + } + + class BusinessTransaction { + - framework : Framework + + BusinessTransaction(framework: Framework) + + processCustomer(resource: Resource, customerId: String, customerData: String) + } + + class Framework { + - lockManager : LockManager + + Framework(lockManager: LockManager) + + tryLockResource(resource: Resource) : boolean + + notifyReleaseLock(resource: Resource) : boolean + + loadCustomerData(resource: Resource) : String + } + + class LockManager { + - lockMap : ConcurrentHashMap + + acquireLock(resource: Resource) : boolean + + releaseLock(resource: Resource) : boolean + } + + class Resource { + - id : String + + Resource(id: String) + + getId() : String + } +} + +App --> BusinessTransaction : "Creates" +BusinessTransaction --> Framework : "Uses" +Framework --> LockManager : "Notifies" +Framework --> Resource : "Interacts with" +LockManager --> Resource : "Locks" +@enduml diff --git a/Implicit-lock-pattern/pom.xml b/Implicit-lock-pattern/pom.xml new file mode 100644 index 000000000000..56a33cb24238 --- /dev/null +++ b/Implicit-lock-pattern/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + Implicit-lock-pattern + + + 23 + 23 + UTF-8 + + + \ No newline at end of file diff --git a/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/App.java b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/App.java new file mode 100644 index 000000000000..b0057f85f7fc --- /dev/null +++ b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/App.java @@ -0,0 +1,34 @@ +package com.iluwatar.implicitlockpattern; + + +/** + * App class serves as the entry point for the simulation. + * It creates resources and processes transactions for different customers. + */ +public class App { + + public static void main(String[] args) { + // Create some sample resources (could be customers, products, etc.) + Resource resource1 = new Resource("Resource1"); + Resource resource2 = new Resource("Resource2"); + Resource resource3 = new Resource("Resource3"); + + // Create a LockManager instance to manage the locks + LockManager lockManager = new LockManager(); + + // Create a Framework instance with the LockManager + Framework framework = new Framework(lockManager); + + // Create a BusinessTransaction instance to simulate processing + BusinessTransaction transaction = new BusinessTransaction(framework); + + // Process customers with their associated resources + transaction.processCustomer(resource1, "456", "Customer data for 456"); + transaction.processCustomer(resource2, "123", "Customer data for 123"); // This will fail to lock + transaction.processCustomer(resource3, "789", "Customer data for 789"); + + // Attempting to process another customer with the same resource should fail + transaction.processCustomer(resource1, "111", "Customer data for 111"); // This will fail to lock again + } +} + diff --git a/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/BusinessTransaction.java b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/BusinessTransaction.java new file mode 100644 index 000000000000..83c5dfb47e9e --- /dev/null +++ b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/BusinessTransaction.java @@ -0,0 +1,42 @@ +package com.iluwatar.implicitlockpattern; + + +/** + * BusinessTransaction class handles the logic of processing customer transactions. + * It works with the Framework to acquire and release locks for resources. + */ +public class BusinessTransaction { + + private final Framework framework; + + // Constructor accepts the Framework to interact with the LockManager + public BusinessTransaction(Framework framework) { + this.framework = framework; + } + + /** + * Processes a customer transaction by acquiring a lock on the corresponding resource. + * + * @param resource the resource to be locked during the transaction + * @param customerId the ID of the customer being processed + * @param customerData the data related to the customer being processed + */ + public void processCustomer(Resource resource, String customerId, String customerData) { + // Print a message indicating which customer is being processed + System.out.println("Processing customer " + customerId + " with data: " + customerData); + + // Try to acquire the lock for the resource + if (framework.tryLockResource(resource)) { + // Simulate some processing (e.g., sleeping for 500 milliseconds) + try { + Thread.sleep(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // Handle interruption + } + // Release the lock after processing is done + framework.notifyReleaseLock(resource); + } else { + System.out.println("Failed to acquire lock for resource: " + resource.getId()); + } + } +} diff --git a/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/Framework.java b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/Framework.java new file mode 100644 index 000000000000..dd5cded027dc --- /dev/null +++ b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/Framework.java @@ -0,0 +1,44 @@ +package com.iluwatar.implicitlockpattern; + +/** + * Framework class interacts with the LockManager to acquire and release locks. + * It simplifies the usage of locking mechanisms for the BusinessTransaction. + */ +public class Framework { + private final LockManager lockManager; + + // Constructor initializes the LockManager instance + public Framework(LockManager lockManager) { + this.lockManager = lockManager; + } + + /** + * Requests to lock a resource via the LockManager. + * + * @param resource the resource to be locked + * @return true if the lock was acquired, false otherwise + */ + public boolean tryLockResource(Resource resource) { + return lockManager.acquireLock(resource); + } + + /** + * Notifies the LockManager to release the lock on the resource. + * + * @param resource the resource to release the lock for + * @return true if the lock was released, false otherwise + */ + public boolean notifyReleaseLock(Resource resource) { + return lockManager.releaseLock(resource); + } + + /** + * Simulates loading customer data. + * + * @param resource the resource to load data for + * @return customer data associated with the resource + */ + public String loadCustomerData(Resource resource) { + return "Customer data for " + resource.getId(); // Example of returning customer data + } +} diff --git a/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/LockManager.java b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/LockManager.java new file mode 100644 index 000000000000..16621bd03cf9 --- /dev/null +++ b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/LockManager.java @@ -0,0 +1,53 @@ +package com.iluwatar.implicitlockpattern; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class LockManager { + + // A thread-safe map to track the locks for each resource by their ID + private final ConcurrentHashMap lockMap = new ConcurrentHashMap<>(); + + /** + * Acquires a lock for the given resource if it's not already locked. + * + * @param resource the resource to acquire the lock for + * @return true if the lock was successfully acquired, false if the resource is already locked + */ + public boolean acquireLock(Resource resource) { + // Acquiring lock for a resource + Lock lock = lockMap.computeIfAbsent(resource.getId(), k -> new ReentrantLock()); + + if (lock.tryLock()) { + try { + System.out.println("Lock acquired for resource: " + resource.getId()); + // Perform actions on the resource + return true; // Lock successfully acquired, returning true + } finally { + lock.unlock(); // Ensure the lock is released + System.out.println("Lock released for resource: " + resource.getId()); + } + } else { + System.out.println("Cannot acquire lock for resource: " + resource.getId() + " - Already locked."); + return false; // Lock could not be acquired, returning false + } + } + + /** + * Releases the lock for a given resource if it is locked. + * + * @param resource the resource to release the lock for + * @return true if the lock was successfully released, false otherwise + */ + public boolean releaseLock(Resource resource) { + Lock lock = lockMap.get(resource.getId()); + if (lock != null && lock instanceof ReentrantLock && ((ReentrantLock) lock).isHeldByCurrentThread()) { + ((ReentrantLock) lock).unlock(); + lockMap.remove(resource.getId()); // Remove lock after releasing + System.out.println("Lock released for resource: " + resource.getId()); + return true; + } + return false; + } +} diff --git a/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/Resource.java b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/Resource.java new file mode 100644 index 000000000000..c0ed1c43614b --- /dev/null +++ b/Implicit-lock-pattern/src/main/java/com/iluwatar/implicitlockpattern/Resource.java @@ -0,0 +1,19 @@ +package com.iluwatar.implicitlockpattern; + +/** + * Resource class represents a resource with a unique identifier. + * This is used as the object that will be locked and processed. + */ +public class Resource { + private final String id; // Unique identifier for the resource + + // Constructor to initialize the resource with an ID + public Resource(String id) { + this.id = id; + } + + // Getter for resource ID + public String getId() { + return id; + } +} diff --git a/Implicit-lock-pattern/src/test/java/com/iluwatar/implicitlockpattern/Apptest.java b/Implicit-lock-pattern/src/test/java/com/iluwatar/implicitlockpattern/Apptest.java new file mode 100644 index 000000000000..407e75ca6c48 --- /dev/null +++ b/Implicit-lock-pattern/src/test/java/com/iluwatar/implicitlockpattern/Apptest.java @@ -0,0 +1,14 @@ +package com.iluwatar.implicitlockpattern; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + // Verifying that the main method of the application runs without any exceptions + assertDoesNotThrow(() -> App.main(new String[]{}), "The application should run without throwing any exceptions"); + } +} diff --git a/Implicit-lock-pattern/src/test/java/com/iluwatar/implicitlockpattern/ImplicitlockTest.java b/Implicit-lock-pattern/src/test/java/com/iluwatar/implicitlockpattern/ImplicitlockTest.java new file mode 100644 index 000000000000..e7a011c2ccea --- /dev/null +++ b/Implicit-lock-pattern/src/test/java/com/iluwatar/implicitlockpattern/ImplicitlockTest.java @@ -0,0 +1,64 @@ +package com.iluwatar.implicitlockpattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ImplicitLockTest { + + private LockManager lockManager; + private Framework framework; + private Resource resource1; + private Resource resource2; + + @BeforeEach + void setUp() { + lockManager = new LockManager(); + framework = new Framework(lockManager); + resource1 = new Resource("Resource1"); + resource2 = new Resource("Resource2"); + } + + @Test + void verifyLockAcquisition() { + // Try to acquire lock on resource1 + assertTrue(framework.tryLockResource(resource1), "Lock should be acquired for resource1"); + + + + // Try to acquire lock on resource2 + assertTrue(framework.tryLockResource(resource2), "Lock should be acquired for resource2"); + } + + @Test + void verifyLockRelease() { + // Acquire lock on resource1 + assertTrue(framework.tryLockResource(resource1), "Lock should be acquired for resource1"); + + // Release lock on resource1 + assertTrue(framework.notifyReleaseLock(resource1), "Lock should be released for resource1"); + + // Try to acquire lock on resource1 again after release + assertTrue(framework.tryLockResource(resource1), "Lock should be acquired for resource1 again after release"); + } + + @Test + void verifyResourceLockedByDifferentThreads() throws InterruptedException { + // Create a thread to lock resource1 + Thread thread1 = new Thread(() -> { + assertTrue(framework.tryLockResource(resource1), "Lock should be acquired for resource1 by thread1"); + }); + + // Create a second thread to try locking the same resource + Thread thread2 = new Thread(() -> { + assertFalse(framework.tryLockResource(resource1), "Lock should not be acquired for resource1 by thread2"); + }); + + thread1.start(); + thread1.join(); + + thread2.start(); + thread2.join(); + } +} diff --git a/pom.xml b/pom.xml index 959643f79fcf..83958df6e1a3 100644 --- a/pom.xml +++ b/pom.xml @@ -34,29 +34,31 @@ 2014-2022 Java Design Patterns Java Design Patterns - - UTF-8 - 4.0.0.4121 - 2.7.5 - 0.8.12 - 1.4 - 4.5.0 - 2.11.0 - 6.0.0 - 1.1.0 - 3.5.1 - 3.6.0 - 4.6 - 2.1.1 - 2.0.16 - 1.5.6 - - https://sonarcloud.io - iluwatar - iluwatar_java-design-patterns - ${project.artifactId} - Java Design Patterns - + + UTF-8 + 4.0.0.4121 + 2.7.5 + 0.8.12 + 1.4 + 4.5.0 + 2.11.0 + 6.0.0 + 1.1.0 + 3.5.1 + 3.6.0 + 4.6 + 2.1.1 + 2.0.16 + 1.5.6 + + https://sonarcloud.io + iluwatar + iluwatar_java-design-patterns + ${project.artifactId} + Java Design Patterns + 23 + 23 + abstract-factory collecting-parameter @@ -218,6 +220,7 @@ function-composition microservices-distributed-tracing microservices-idempotent-consumer + Implicit-lock-pattern