-
-
Notifications
You must be signed in to change notification settings - Fork 735
Add approach for Bank Account #3031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
43a2d4b
Add approach for Bank Account
kahgoh 5e603c1
Fix wording and spelling
kahgoh 47100fe
Replace "syncrhonized objects" with "synchronized statements"
kahgoh 90247d6
Add explanation for lock in synchronized method option
kahgoh 05c0245
Add missing word
kahgoh 85c3b7b
Fix grammar
kahgoh 21e2405
Merge branch 'main' into approach/bank-account
jagdish-15 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
{ | ||
"introduction": { | ||
"authors": [ | ||
"kahgoh" | ||
] | ||
}, | ||
"approaches": [ | ||
{ | ||
"uuid": "78d753b0-aa58-43dc-83c0-9ea41496de84", | ||
"slug": "synchronized-methods", | ||
"title": "Synchronized methods", | ||
"blurb": "Use synchronized methods to prevent methods from running simultaneously in different threads", | ||
"authors": [ | ||
"kahgoh" | ||
] | ||
}, | ||
{ | ||
"uuid": "5f3b0152-02eb-40d5-9104-5edc30b4447e", | ||
"slug": "synchronized-statements", | ||
"title": "Synchronized statements", | ||
"blurb": "Use an object to prevent threads from running blocks of code at the same time", | ||
"authors": [ | ||
"kahgoh" | ||
] | ||
}, | ||
{ | ||
"uuid": "0acd6f2b-27d0-4ae6-9c22-22b0b2047039", | ||
"slug": "reentrant-lock", | ||
"title": "Reentrant lock", | ||
"blurb": "Use ", | ||
"authors": [ | ||
"kahgoh" | ||
] | ||
}, | ||
{ | ||
"uuid": "4ad42f88-f750-4af9-bbbd-d8b2dc5e8078", | ||
"slug": "readwrite-lock", | ||
"title": "Readwrite lock", | ||
"blurb": "Use separate read and write locks to achieve greater concurrency", | ||
"authors": [ | ||
"kahgoh" | ||
] | ||
} | ||
] | ||
} |
351 changes: 351 additions & 0 deletions
351
exercises/practice/bank-account/.approaches/introduction.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,351 @@ | ||
# Introduction | ||
|
||
In Bank Account, you are tasked with implementing a number of operations that can be performed on a bank account. | ||
However, these operations may be performed by multiple threads at the same time. | ||
|
||
## General guidance | ||
|
||
The key to solving Bank Account is to prevent an account from being updated from multiple threads at the same time. | ||
For example, consider an account that begins with $0. | ||
A $10 deposit is made twice. | ||
Each transaction starts a thread. | ||
If the threads happen to start simultaneously, they might both see the account starting $0. | ||
They each add $10 to this amount and update the account accordingly. | ||
In this case, each thread sets the amount to $10 even though the transaction was made twice. | ||
|
||
The problem here is both threads saw that was $0 in the account prior to adding the deposit. | ||
Instead, each thread needs to take turn to process the deposit for the account so that they can process the transaction one at a time. | ||
This way, the later thread can "see" the deposited amount from the first thread. | ||
|
||
## Approach: Synchronized methods | ||
|
||
```java | ||
class BankAccount { | ||
private int balance = 0; | ||
private boolean isClosed = true; | ||
|
||
synchronized void open() throws BankAccountActionInvalidException { | ||
if (!isClosed) { | ||
throw new BankAccountActionInvalidException("Account already open"); | ||
} | ||
isClosed = false; | ||
balance = 0; | ||
} | ||
|
||
synchronized void close() throws BankAccountActionInvalidException { | ||
if (isClosed) { | ||
throw new BankAccountActionInvalidException("Account not open"); | ||
} | ||
isClosed = true; | ||
} | ||
|
||
synchronized int getBalance() throws BankAccountActionInvalidException { | ||
checkIfClosed(); | ||
return balance; | ||
} | ||
|
||
synchronized void deposit(int amount) throws BankAccountActionInvalidException { | ||
checkIfClosed(); | ||
checkIfValidAmount(amount); | ||
|
||
balance += amount; | ||
} | ||
|
||
synchronized void withdraw(int amount) throws BankAccountActionInvalidException { | ||
checkIfClosed(); | ||
checkIfValidAmount(amount); | ||
checkIfEnoughMoneyInAccount(amount); | ||
|
||
balance -= amount; | ||
} | ||
|
||
private void checkIfValidAmount(int amount) throws BankAccountActionInvalidException { | ||
if (amount < 0) { | ||
throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount"); | ||
} | ||
} | ||
|
||
private void checkIfEnoughMoneyInAccount(int amount) throws BankAccountActionInvalidException { | ||
if (balance == 0) { | ||
throw new BankAccountActionInvalidException("Cannot withdraw money from an empty account"); | ||
} | ||
if (balance - amount < 0) { | ||
throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account"); | ||
} | ||
} | ||
|
||
private void checkIfClosed() throws BankAccountActionInvalidException { | ||
if (isClosed) { | ||
throw new BankAccountActionInvalidException("Account closed"); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
For more information, check the [Synchronized methods approach][approach-synchronized-methods]. | ||
|
||
## Approach: Synchronized statements | ||
|
||
```java | ||
class BankAccount { | ||
private final Object lock = new Object(); | ||
private int balance = 0; | ||
private boolean isClosed = true; | ||
|
||
void open() throws BankAccountActionInvalidException { | ||
synchronized(lock) { | ||
if (!isClosed) { | ||
throw new BankAccountActionInvalidException("Account already open"); | ||
} | ||
isClosed = false; | ||
balance = 0; | ||
} | ||
} | ||
|
||
void close() throws BankAccountActionInvalidException { | ||
synchronized(lock) { | ||
if (isClosed) { | ||
throw new BankAccountActionInvalidException("Account not open"); | ||
} | ||
isClosed = true; | ||
} | ||
} | ||
|
||
int getBalance() throws BankAccountActionInvalidException { | ||
synchronized(lock) { | ||
checkIfClosed(); | ||
return balance; | ||
} | ||
} | ||
|
||
void deposit(int amount) throws BankAccountActionInvalidException { | ||
synchronized(lock) { | ||
checkIfClosed(); | ||
checkIfValidAmount(amount); | ||
|
||
balance += amount; | ||
} | ||
} | ||
|
||
void withdraw(int amount) throws BankAccountActionInvalidException { | ||
synchronized(lock) { | ||
checkIfClosed(); | ||
checkIfValidAmount(amount); | ||
checkIfEnoughMoneyInAccount(amount); | ||
|
||
balance -= amount; | ||
} | ||
} | ||
|
||
private void checkIfValidAmount(int amount) throws BankAccountActionInvalidException { | ||
if (amount < 0) { | ||
throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount"); | ||
} | ||
} | ||
|
||
private void checkIfEnoughMoneyInAccount(int amount) throws BankAccountActionInvalidException { | ||
if (balance == 0) { | ||
throw new BankAccountActionInvalidException("Cannot withdraw money from an empty account"); | ||
} | ||
if (balance - amount < 0) { | ||
throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account"); | ||
} | ||
} | ||
|
||
private void checkIfClosed() throws BankAccountActionInvalidException { | ||
if (isClosed) { | ||
throw new BankAccountActionInvalidException("Account closed"); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
For more information, check the [Synchronized statements approach][approach-synchronized-statements]. | ||
|
||
## Approach: Reentrant lock | ||
|
||
```java | ||
import java.util.concurrent.locks.ReentrantLock; | ||
|
||
class BankAccount { | ||
|
||
private final ReentrantLock lock = new ReentrantLock(); | ||
|
||
private boolean isOpen = false; | ||
private int balance = 0; | ||
|
||
void open() throws BankAccountActionInvalidException { | ||
lock.lock(); | ||
try { | ||
if (isOpen) { | ||
throw new BankAccountActionInvalidException("Account already open"); | ||
} | ||
isOpen = true; | ||
balance = 0; | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
void close() throws BankAccountActionInvalidException { | ||
lock.lock(); | ||
try { | ||
if (!isOpen) { | ||
throw new BankAccountActionInvalidException("Account not open"); | ||
} | ||
isOpen = false; | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
int getBalance() throws BankAccountActionInvalidException { | ||
lock.lock(); | ||
try { | ||
if (!isOpen) { | ||
throw new BankAccountActionInvalidException("Account closed"); | ||
} | ||
return balance; | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
void deposit(int amount) throws BankAccountActionInvalidException { | ||
lock.lock(); | ||
try { | ||
if (!isOpen) { | ||
throw new BankAccountActionInvalidException("Account closed"); | ||
} | ||
if (amount < 0) { | ||
throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount"); | ||
} | ||
balance += amount; | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
void withdraw(int amount) throws BankAccountActionInvalidException { | ||
lock.lock(); | ||
try { | ||
if (!isOpen) { | ||
throw new BankAccountActionInvalidException("Account closed"); | ||
} | ||
if (amount > balance) { | ||
throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account"); | ||
} | ||
if (amount < 0) { | ||
throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount"); | ||
} | ||
balance -= amount; | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
} | ||
``` | ||
|
||
For more information, check the [Reentrant lock approach][approach-reentrant-lock]. | ||
|
||
## Approach: Read write lock | ||
|
||
```java | ||
import java.util.concurrent.locks.ReadWriteLock; | ||
import java.util.concurrent.locks.ReentrantLock; | ||
import java.util.concurrent.locks.ReentrantReadWriteLock; | ||
|
||
class BankAccount { | ||
|
||
private final ReadWriteLock lock = new ReentrantReadWriteLock(); | ||
|
||
private boolean isOpen = false; | ||
|
||
private int balance = 0; | ||
|
||
void open() throws BankAccountActionInvalidException { | ||
lock.writeLock().lock(); | ||
try { | ||
if (isOpen) { | ||
throw new BankAccountActionInvalidException("Account already open"); | ||
} | ||
isOpen = true; | ||
balance = 0; | ||
} finally { | ||
lock.writeLock().unlock(); | ||
} | ||
} | ||
|
||
void close() throws BankAccountActionInvalidException { | ||
lock.writeLock().lock(); | ||
try { | ||
if (!isOpen) { | ||
throw new BankAccountActionInvalidException("Account not open"); | ||
} | ||
isOpen = false; | ||
} finally { | ||
lock.writeLock().unlock(); | ||
} | ||
} | ||
|
||
int getBalance() throws BankAccountActionInvalidException { | ||
lock.readLock().lock(); | ||
try { | ||
if (!isOpen) { | ||
throw new BankAccountActionInvalidException("Account closed"); | ||
} | ||
return balance; | ||
} finally { | ||
lock.readLock().unlock(); | ||
} | ||
} | ||
|
||
void deposit(int amount) throws BankAccountActionInvalidException { | ||
lock.writeLock().lock(); | ||
try { | ||
if (!isOpen) { | ||
throw new BankAccountActionInvalidException("Account closed"); | ||
} | ||
if (amount < 0) { | ||
throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount"); | ||
} | ||
balance += amount; | ||
} finally { | ||
lock.writeLock().unlock(); | ||
} | ||
} | ||
|
||
void withdraw(int amount) throws BankAccountActionInvalidException { | ||
lock.writeLock().lock(); | ||
try { | ||
if (!isOpen) { | ||
throw new BankAccountActionInvalidException("Account closed"); | ||
} | ||
if (amount > balance) { | ||
throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account"); | ||
} | ||
if (amount < 0) { | ||
throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount"); | ||
} | ||
balance -= amount; | ||
} finally { | ||
lock.writeLock().unlock(); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
For more information, check the [Read write lock approach][approach-read-write-lock]. | ||
|
||
## Which approach to use? | ||
|
||
- The synchronized methods is the simplest, requiring no extra objects to be created. | ||
- Synchronized statements provide greater control over which code statements are performed with a lock and which object is to be used as the lock. | ||
- The read write lock allows greater concurrency by letting multiple read operations, such as `getBalance`, to run in parallel. | ||
kahgoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
However, it requires the lock to be explicitly released. | ||
|
||
[approach-read-write-lock]: https://exercism.org/tracks/java/exercises/bank-account/approaches/read-write-lock | ||
kahgoh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
[approach-reentrant-lock]: https://exercism.org/tracks/java/exercises/bank-acconuunt/approaches/reentrant-lock | ||
[approach-synchronized-methods]: https://exercism.org/tracks/java/exercises/bank-account/approaches/synchronized-methods | ||
[approach-synchronized-statements]: https://exercism.org/tracks/java/exercises/bank-account/approaches/synchronzied-statements |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.