diff --git a/monitor/README.md b/monitor/README.md new file mode 100644 index 000000000..59eb2ba0c --- /dev/null +++ b/monitor/README.md @@ -0,0 +1,71 @@ +--- +layout: pattern +title: Monitor +folder: monitor +permalink: /patterns/monitor/ +categories: Concurrency +language: en +tags: + - Performance +--- + +## Intent +Monitor pattern is used to create thread-safe objects and prevent conflicts between threads in concurrent applications. + +## Explanation + +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. + +Wikipedia says + +> In concurrent programming (also known as parallel programming), a monitor is a synchronization construct that allows threads to have both mutual exclusion and the ability to wait (block) for a certain condition to become false. Monitors also have a mechanism for signaling other threads that their condition has been met. + +**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 { + + 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()); + } + } +``` + +getBalance always return total amount and the total amount should be same after each transfers + +``` + private synchronized int getBalance() { + int balance = 0; + for (int account : accounts) { + balance += account; + } + return balance; + } + } +``` + +## Class diagram +![alt text](./etc/monitor.urm.png "Monitor class diagram") + +## Applicability +Use the Monitor pattern when + +* we have a shared resource and there is critical section . +* you want to create thread-safe objects . +* you want to achieve mutual exclusion in high level programming language . diff --git a/monitor/etc/monitor.urm.png b/monitor/etc/monitor.urm.png new file mode 100644 index 000000000..c00e70e3f Binary files /dev/null and b/monitor/etc/monitor.urm.png differ diff --git a/monitor/etc/monitor.urm.puml b/monitor/etc/monitor.urm.puml new file mode 100644 index 000000000..075ef8520 --- /dev/null +++ b/monitor/etc/monitor.urm.puml @@ -0,0 +1,12 @@ +@startuml +Main - Bank : use +class Main{ + + main(args : String[]) : void +} +class Bank{ + - accounts : int[] + + Bank (accountNum : int , baseAccount : int) + + transfer(accountA : int , accountB : int , amount : int) : void {synchronized} + - getBalance() : void {synchronized} +} +@enduml diff --git a/monitor/pom.xml b/monitor/pom.xml new file mode 100644 index 000000000..46e10f17d --- /dev/null +++ b/monitor/pom.xml @@ -0,0 +1,59 @@ + + + + 4.0.0 + + java-design-patterns + com.iluwatar + 1.24.0-SNAPSHOT + + monitor + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.abstractdocument.Main + + + + + + + + + diff --git a/monitor/src/main/java/com/iluwatar/monitor/Bank.java b/monitor/src/main/java/com/iluwatar/monitor/Bank.java new file mode 100644 index 000000000..d4defa6d1 --- /dev/null +++ b/monitor/src/main/java/com/iluwatar/monitor/Bank.java @@ -0,0 +1,37 @@ +package com.iluwatar.monitor; + +import java.util.Arrays; +import java.util.logging.Logger; + +// Bank class implements the Monitor pattern +public class Bank { + + 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()); + } + } + + public synchronized int getBalance() { + int balance = 0; + for (int account : accounts) { + balance += account; + } + return balance; + } + + public int[] getAccounts() { + return accounts; + } +} diff --git a/monitor/src/main/java/com/iluwatar/monitor/Main.java b/monitor/src/main/java/com/iluwatar/monitor/Main.java new file mode 100644 index 000000000..c1e1bcccb --- /dev/null +++ b/monitor/src/main/java/com/iluwatar/monitor/Main.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * Copyright © 2014-2021 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.monitor; + +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +/** + *

The Monitor pattern is used in concurrent algorithms to achieve mutual exclusion.

+ * + *

Bank is a simple class that transfers money from an account to another account using + * {@link Bank#transfer}. It can also return the balance of the bank account stored in the bank.

+ * + *

Main class uses ThreadPool to run threads that do transactions on the bank accounts.

+ */ + +public class Main { + + public static void main(String[] args) { + Logger logger = Logger.getLogger("monitor"); + var bank = new Bank(4, 1000, logger); + Runnable runnable = () -> { + try { + Thread.sleep((long) (Math.random() * 1000)); + Random random = new Random(); + for (int i = 0; i < 1000000; i++) + bank.transfer(random.nextInt(4), random.nextInt(4), (int) (Math.random() * 1000)); + } catch (InterruptedException e) { + logger.info(e.getMessage()); + } + }; + ExecutorService executorService = Executors.newFixedThreadPool(5); + for (int i = 0; i < 5; i++) { + executorService.execute(runnable); + } + } +} \ No newline at end of file diff --git a/monitor/src/main/test/java/com/iluwater/java/BankTest.java b/monitor/src/main/test/java/com/iluwater/java/BankTest.java new file mode 100644 index 000000000..eab7d7a3c --- /dev/null +++ b/monitor/src/main/test/java/com/iluwater/java/BankTest.java @@ -0,0 +1,55 @@ +package com.iluwater.java; + +import com.iluwatar.monitor.Bank; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; + +import java.util.logging.Logger; + +public class BankTest { + + private static Bank bank; + private static final int ACCOUNT_NUM = 4; + private static final int BASE_AMOUNT = 1000; + private static final Logger LOGGER = Logger.getLogger("monitor"); + + @BeforeAll + public static void Setup() { + bank = new Bank(ACCOUNT_NUM, BASE_AMOUNT, LOGGER); + } + + @Test + public void GetAccountHaveNotBeNull() { + assertNotNull(bank.getAccounts()); + } + + @Test + public void LengthOfAccountsHaveToEqualsToAccountNumConstant() { + assumeTrue(bank.getAccounts() != null); + assertEquals(ACCOUNT_NUM, bank.getAccounts().length); + } + + @Test + public void TransferMethodHaveToTransferAmountFromAnAccountToOtherAccount() { + bank.transfer(0, 1, 1000); + int accounts[] = bank.getAccounts(); + assertEquals(0, accounts[0]); + assertEquals(2000, 2000); + } + + @Test + public void BalanceHaveToBeOK() { + assertEquals(4000, bank.getBalance()); + } + + + @AfterAll + public static void TearDown() { + bank = null; + } + +}