docs: update monitor

This commit is contained in:
Ilkka Seppälä
2024-05-09 12:34:07 +03:00
parent bf6456ba69
commit 36a9eea769
2 changed files with 119 additions and 59 deletions
+117 -50
View File
@@ -3,21 +3,29 @@ title: Monitor
category: Concurrency
language: en
tag:
- Performance
- Encapsulation
- Fault tolerance
- Isolation
- Synchronization
- Thread management
---
## Also known as
Monitor object pattern
* Synchronized Block
## Intent
The primary intent is to provide a structured and controlled way for multiple threads or processes to safely access and
manipulate shared resources, such as variables, data structures, or critical sections of code, without causing conflicts
or race conditions.
The Monitor design pattern is used to synchronize concurrent operations by encapsulating shared resources in such a way that only one thread can access them at a time, ensuring thread safety.
## Explanation
Real-world example
> Imagine a shared office printer that several employees need to use. The printer can only handle one print job at a time to avoid mixing up pages from different documents. This scenario is analogous to the Monitor design pattern in programming.
>
> In this example, the printer represents the shared resource, and the employees are analogous to threads. A system is set up where each employee must request access to the printer before starting their print job. This system ensures that only one employee (or "thread") can use the printer at a time, preventing any overlap or interference between jobs. Once a print job is complete, the next employee in the queue can access the printer. This mechanism mirrors the Monitor pattern's way of controlling access to a shared resource, ensuring orderly and safe use by multiple "threads" (employees).
In plain words
> Monitor pattern is used to enforce single-threaded access to data. Only one thread at a time is allowed to execute code within the monitor object.
@@ -28,51 +36,92 @@ Wikipedia says
**Programmatic Examples**
Consider there is a bank that transfers money from an account to another account with transfer method . it is `synchronized` mean just one thread can access to this method because if many threads access to it and transfer money from an account to another account in same time balance changed !
```
class Bank {
The Monitor design pattern is a synchronization technique used in concurrent programming to ensure that only one thread can execute a particular section of code at a time. It is a method of wrapping and hiding the synchronization primitives (like semaphores or locks) within the methods of an object. This pattern is useful in situations where race conditions could occur.
private int[] accounts;
Logger logger;
public Bank(int accountNum, int baseAmount, Logger logger) {
this.logger = logger;
accounts = new int[accountNum];
Arrays.fill(accounts, baseAmount);
}
public synchronized void transfer(int accountA, int accountB, int amount) {
if (accounts[accountA] >= amount) {
accounts[accountB] += amount;
accounts[accountA] -= amount;
logger.info("Transferred from account :" + accountA + " to account :" + accountB + " , amount :" + amount + " . balance :" + getBalance());
}
}
In the provided code, the `Bank` class is an example of the Monitor pattern. The `Bank` class has several methods that are marked as `synchronized`. This means that only one thread can execute these methods at a time, ensuring that the bank's state remains consistent even when accessed from multiple threads.
Here is a simplified version of the `Bank` class with additional comments:
```java
public class Bank {
@Getter
private final int[] accounts;
public Bank(int accountNum, int baseAmount) {
accounts = new int[accountNum];
Arrays.fill(accounts, baseAmount);
}
public synchronized void transfer(int accountA, int accountB, int amount) {
// Only one thread can execute this method at a time due to the 'synchronized' keyword.
if (accounts[accountA] >= amount && accountA != accountB) {
accounts[accountB] += amount;
accounts[accountA] -= amount;
}
}
public synchronized int getBalance() {
// Only one thread can execute this method at a time due to the 'synchronized' keyword.
int balance = 0;
for (int account : accounts) {
balance += account;
}
return balance;
}
public synchronized int getBalance(int accountNumber) {
// Only one thread can execute this method at a time due to the 'synchronized' keyword.
return accounts[accountNumber];
}
}
```
getBalance always return total amount and the total amount should be same after each transfers
In the `Main` class, multiple threads are created to perform transactions on the bank accounts. The `Bank` class, acting as a monitor, ensures that these transactions are performed in a thread-safe manner.
```java
public class Main {
private static final int NUMBER_OF_THREADS = 5;
private static final int BASE_AMOUNT = 1000;
private static final int ACCOUNT_NUM = 4;
public static void runner(Bank bank, CountDownLatch latch) {
try {
SecureRandom random = new SecureRandom();
Thread.sleep(random.nextInt(1000));
for (int i = 0; i < 1000000; i++) {
bank.transfer(random.nextInt(4), random.nextInt(4), random.nextInt(0, BASE_AMOUNT));
}
latch.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
var bank = new Bank(ACCOUNT_NUM, BASE_AMOUNT);
var latch = new CountDownLatch(NUMBER_OF_THREADS);
var executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
executorService.execute(() -> runner(bank, latch));
}
latch.await();
}
}
```
private synchronized int getBalance() {
int balance = 0;
for (int account : accounts) {
balance += account;
}
return balance;
}
}
```
In this example, the `Bank` class is the monitor, and the `transfer` method is the critical section that needs to be executed in a mutually exclusive manner. The `synchronized` keyword in Java is used to implement the Monitor pattern, ensuring that only one thread can execute the `transfer` method at a time.
## Class diagram
![alt text](./etc/monitor.urm.png "Monitor class diagram")
![Monitor](./etc/monitor.urm.png "Monitor class diagram")
## Applicability
The Monitor design pattern should be used in situations where you have shared resources that need to be accessed and
manipulated by multiple threads or processes concurrently. This pattern is particularly useful in scenarios where
synchronization is necessary to prevent race conditions, data corruption, and inconsistent states. Here are some
situations where you should consider using the Monitor pattern:
The Monitor design pattern should be used in situations where you have shared resources that need to be accessed and manipulated by multiple threads or processes concurrently. This pattern is particularly useful in scenarios where synchronization is necessary to prevent race conditions, data corruption, and inconsistent states. Here are some situations where you should consider using the Monitor pattern:
1. **Shared Data**: When your application involves shared data structures, variables, or resources that need to be accessed and updated by multiple threads. Monitors ensure that only one thread can access the shared resource at a time, preventing conflicts and ensuring data consistency.
@@ -90,16 +139,34 @@ situations where you should consider using the Monitor pattern:
8. **Improved Maintainability**: When you want to encapsulate synchronization logic and shared resource management within a single object, improving code organization and making it easier to reason about concurrency-related code.
However, it's important to note that the Monitor pattern might not be the best fit for all concurrency scenarios. In
some cases, other synchronization mechanisms like locks, semaphores, or concurrent data structures might be more
suitable. Additionally, modern programming languages and frameworks often provide higher-level concurrency constructs
that abstract away the complexities of low-level synchronization.
However, it's important to note that the Monitor pattern might not be the best fit for all concurrency scenarios. In some cases, other synchronization mechanisms like locks, semaphores, or concurrent data structures might be more suitable. Additionally, modern programming languages and frameworks often provide higher-level concurrency constructs that abstract away the complexities of low-level synchronization.
Before applying the Monitor pattern, it's recommended to thoroughly analyze your application's concurrency requirements
and choose the synchronization approach that best suits your needs, taking into consideration factors like performance,
complexity, and available language features.
Before applying the Monitor pattern, it's recommended to thoroughly analyze your application's concurrency requirements and choose the synchronization approach that best suits your needs, taking into consideration factors like performance, complexity, and available language features.
## Related patterns
## Known Uses
* Active object
* Double-checked locking
* Java's synchronized methods and blocks.
* Implementations of concurrent data structures like Vector and Hashtable in the Java Collections Framework.
## Consequences
Benefits:
* Ensures mutual exclusion, preventing race conditions.
* Simplifies the complexity of thread management by providing a clear structure for resource access.
Trade-offs:
* Can lead to decreased performance due to locking overhead.
* Potential for deadlocks if not carefully designed.
## Related Patterns
Semaphore: Used to control access to a common resource by multiple threads; Monitor uses a binary semaphore concept at its core.
Mutex: Another mechanism for ensuring mutual exclusion; Monitor is a higher-level construct often implemented using mutexes.
## Credits
* [Concurrency: State Models & Java Programs](https://amzn.to/4dxxjUX)
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
* [Java Concurrency in Practice](https://amzn.to/4aRMruW)
@@ -48,12 +48,14 @@
package com.iluwatar.monitor;
import java.util.Arrays;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/** Bank Definition. */
@Slf4j
public class Bank {
@Getter
private final int[] accounts;
/**
@@ -113,13 +115,4 @@ public class Bank {
public synchronized int getBalance(int accountNumber) {
return accounts[accountNumber];
}
/**
* Get all accounts.
*
* @return accounts
*/
public int[] getAccounts() {
return accounts;
}
}