implement thread-safe banking operations with ordered locking
This commit is contained in:
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="HW-09-Advanced-Multithreading" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+20
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://maven.devneeds.ir/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="25" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# Theory Answers – Concurrent Banking System
|
||||
|
||||
## 1. Atomic Variables & Synchronization
|
||||
|
||||
### 1.1 What are atomic variables?
|
||||
|
||||
Atomic variables are special types that support **lock‑free, thread‑safe** operations on a single variable. They guarantee that certain compound actions (like read‑modify‑write) are performed as one indivisible step – no other thread can see an intermediate state.
|
||||
|
||||
**Why do we need them?**
|
||||
In a multi‑threaded environment, a plain `int counter` shared among threads can produce wrong results because `counter++` is actually three separate steps: read, add, write. Without proper synchronization, threads can interleave these steps and lose updates. Atomic variables solve this using low‑level hardware support (e.g., Compare‑And‑Swap, CAS) without the overhead of locks.
|
||||
|
||||
**How they differ from ordinary variables:**
|
||||
Ordinary variables offer no built‑in guarantees about visibility or atomicity. If multiple threads access them without external synchronization, you get data races and inconsistent values. Atomic variables, on the other hand, ensure that each operation is applied completely before any other thread can see a change – and they also enforce memory visibility (happens‑before relationships).
|
||||
|
||||
---
|
||||
|
||||
### 1.2 Four classes from `java.util.concurrent.atomic`
|
||||
|
||||
- `AtomicInteger` – for `int` values.
|
||||
- `AtomicLong` – for `long` values.
|
||||
- `AtomicBoolean` – for `boolean` flags.
|
||||
- `AtomicReference<V>` – for object references.
|
||||
|
||||
**Typical use case for `AtomicInteger`:**
|
||||
Imagine a web server that counts the total number of processed requests. Every worker thread increments this counter after handling a request. Using `AtomicInteger` with `incrementAndGet()` guarantees that the final count is always accurate, no matter how many threads are running concurrently, and it does so without blocking.
|
||||
|
||||
---
|
||||
|
||||
### 1.3 Locks vs. Atomic Variables – when to use which?
|
||||
|
||||
| **Aspect** | **Atomic Variables** | **Locks (e.g., `synchronized`, `ReentrantLock`)** |
|
||||
|--------------------------|-----------------------------------------------------------|-----------------------------------------------------------|
|
||||
| Overhead | Very low (CAS is usually a single CPU instruction) | Higher (context switching, queue management) |
|
||||
| Complexity | Best for simple updates on a single variable | Needed for compound actions across multiple resources |
|
||||
| Blocking behaviour | Non‑blocking – retry on failure | Blocking – thread waits if lock is held |
|
||||
| Flexibility | Limited to atomic operations on one variable | Can handle multiple conditions, timeouts, fair ordering |
|
||||
|
||||
**When a lock is a better choice:**
|
||||
- When you need to update several related variables together (e.g., transfer money between two accounts – both must be locked).
|
||||
- When you need conditional waiting (e.g., wait until the balance is sufficient).
|
||||
- When the contention is low and lock overhead is negligible compared to the work done inside the critical section.
|
||||
|
||||
**When an atomic variable is preferable:**
|
||||
- For simple counters, accumulators, or status flags.
|
||||
- When you have many threads and you want to avoid blocking and reduce latency.
|
||||
- When you are building non‑blocking data structures.
|
||||
|
||||
---
|
||||
|
||||
### 🎯 Bonus Task – Demonstration Program
|
||||
|
||||
Below is a small Java program that launches multiple threads to increment both a plain `int` and an `AtomicInteger`. Run it and you’ll see the plain integer often ends up with a value less than the expected total, while the `AtomicInteger` always gives the correct result – clearly showing the race condition.
|
||||
|
||||
```java
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class AtomicDemo {
|
||||
private static int plainCounter = 0;
|
||||
private static AtomicInteger atomicCounter = new AtomicInteger(0);
|
||||
private static final int THREADS = 10;
|
||||
private static final int INCREMENTS_PER_THREAD = 1000;
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
Thread[] threads = new Thread[THREADS];
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
threads[i] = new Thread(() -> {
|
||||
for (int j = 0; j < INCREMENTS_PER_THREAD; j++) {
|
||||
plainCounter++; // Not atomic
|
||||
atomicCounter.incrementAndGet(); // Atomic
|
||||
}
|
||||
});
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
for (Thread t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
System.out.println("Plain counter final value: " + plainCounter);
|
||||
System.out.println("Atomic counter final value: " + atomicCounter.get());
|
||||
// Expected: 10000 (10 * 1000)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,14 @@
|
||||
package dev.banking.model;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class BankAccount {
|
||||
|
||||
private final int accountId;
|
||||
private long balance;
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
/*
|
||||
* Students may introduce additional fields
|
||||
* such as:
|
||||
* - Lock / ReentrantLock
|
||||
* - ReadWriteLock
|
||||
* - Object monitor
|
||||
* - etc.
|
||||
*/
|
||||
|
||||
public BankAccount(int accountId, long initialBalance) {
|
||||
this.accountId = accountId;
|
||||
@@ -23,56 +19,53 @@ public class BankAccount {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* Return the current balance in a thread-safe way.
|
||||
*
|
||||
* Requirements:
|
||||
* - Must be safe under concurrent reads/writes
|
||||
* - Should not block unnecessarily if using read/write locks
|
||||
*/
|
||||
public long getBalance() {
|
||||
throw new UnsupportedOperationException("TODO: implement thread-safe balance read");
|
||||
lock.lock();
|
||||
try {
|
||||
return balance;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* Increase balance atomically.
|
||||
*
|
||||
* Requirements:
|
||||
* - Must not lose updates under concurrency
|
||||
*/
|
||||
public void deposit(long amount) {
|
||||
throw new UnsupportedOperationException("TODO: implement thread-safe deposit");
|
||||
lock.lock();
|
||||
try {
|
||||
balance += amount;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* Decrease balance atomically.
|
||||
*
|
||||
* Requirements:
|
||||
* - Must not cause race conditions
|
||||
* - Negative balance handling is NOT required unless you decide
|
||||
* to extend the system (optional)
|
||||
*/
|
||||
public void withdraw(long amount) {
|
||||
throw new UnsupportedOperationException("TODO: implement thread-safe withdraw");
|
||||
lock.lock();
|
||||
try {
|
||||
balance -= amount;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* Transfer money between two accounts atomically.
|
||||
*
|
||||
* IMPORTANT REQUIREMENTS:
|
||||
* - Must be atomic (no partial transfer)
|
||||
* - Must be deadlock-free
|
||||
* - Must protect both source and target accounts
|
||||
*
|
||||
* HINT:
|
||||
* - Consider global lock ordering using accountId
|
||||
* - Or tryLock with retry strategy
|
||||
*/
|
||||
|
||||
public void transfer(BankAccount target, long amount) {
|
||||
throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer");
|
||||
if (this == target) {
|
||||
return;
|
||||
}
|
||||
BankAccount firstLock = this.accountId < target.accountId ? this : target;
|
||||
BankAccount secondLock = this.accountId < target.accountId ? target : this;
|
||||
|
||||
firstLock.lock.lock();
|
||||
try {
|
||||
secondLock.lock.lock();
|
||||
try {
|
||||
this.balance -= amount;
|
||||
target.balance += amount;
|
||||
} finally {
|
||||
secondLock.lock.unlock();
|
||||
}
|
||||
} finally {
|
||||
firstLock.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user