From d34292837087e3169fcc88b124fd1aa6a71edc82 Mon Sep 17 00:00:00 2001 From: Hesam Ghazi Date: Fri, 17 Jul 2026 03:26:58 +0330 Subject: [PATCH] Theorical questions are in REPORT.md and practical tasks are implemented --- .idea/.gitignore | 10 ++ .idea/compiler.xml | 13 +++ .idea/encodings.xml | 7 ++ .idea/jarRepositories.xml | 20 ++++ .idea/misc.xml | 12 +++ .idea/vcs.xml | 6 ++ REPORT.md | 75 +++++++++++++ .../java/dev/banking/model/BankAccount.java | 101 ++++++++++-------- 8 files changed, 202 insertions(+), 42 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 REPORT.md diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -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/ diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..812c3f9 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..eba6e1f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/REPORT.md b/REPORT.md new file mode 100644 index 0000000..6fc933f --- /dev/null +++ b/REPORT.md @@ -0,0 +1,75 @@ +# REPORT +### Hesam Ghazi +### 403222015 +## 1. Atomic Variables +Atomic variables provide thread-safe single-variable operations performed atomically using CPU-supported compare-and-swap (CAS) instructions. Unlike ordinary variables, they prevent race conditions without explicit synchronization for simple operations. + +## 2. Atomic Classes +- `AtomicInteger` +- `AtomicLong` +- `AtomicBoolean` +- `AtomicReference` + +**Use case:** `AtomicInteger` is commonly used as a thread-safe counter shared among multiple threads. + +## 3. Locks vs Atomic Variables +**Atomic variables** +- Best for simple read-modify-write operations. +- Non-blocking and usually faster under low contention. +- Limited to simple operations. + +**Locks** +- Suitable for protecting multiple variables or complex critical sections. +- Easier to implement compound operations atomically. +- Introduce blocking and context-switch overhead. + +## Bonus Task + +```java +import java.util.concurrent.atomic.AtomicInteger; + +public class AtomicDemo { + static int normal = 0; + static AtomicInteger atomic = new AtomicInteger(0); + + public static void main(String[] args) throws Exception { + Thread[] threads = new Thread[10]; + for (int i = 0; i < threads.length; i++) { + threads[i] = new Thread(() -> { + for (int j = 0; j < 100000; j++) { + normal++; + atomic.incrementAndGet(); + } + }); + } + for (Thread t : threads) t.start(); + for (Thread t : threads) t.join(); + + System.out.println("Normal: " + normal); + System.out.println("Atomic: " + atomic.get()); + } +} +``` + +Expected: `AtomicInteger` always prints 1000000, while `normal` is usually smaller because of race conditions. + +## 4. Correct but Poor Performance +A program may be race-free but still scale poorly because: +1. High lock contention forces threads to wait. +2. Excessive synchronization increases overhead. +3. False sharing and cache coherence traffic reduce CPU efficiency. +4. Frequent blocking decreases parallelism. + +## 5. Why More Threads Can Hurt +- **Context switching:** CPU spends time switching threads. +- **Contention:** Threads compete for shared resources. +- **Cache coherence:** Shared data invalidates CPU caches. +- **Synchronization overhead:** Locks and coordination consume execution time. +- Too many threads may exceed available CPU cores, reducing throughput. + +## 6. Why Deadlocks Often Appear Only in Production +Deadlocks depend on thread scheduling, which is nondeterministic. Testing usually explores only a small subset of possible execution orders, whereas production workloads create many timing combinations. + +Two strategies to expose deadlocks: +1. Perform stress tests with many threads and randomized execution timing. +2. Insert artificial delays (sleep/yield) around lock acquisition to increase unfavorable interleavings. diff --git a/src/main/java/dev/banking/model/BankAccount.java b/src/main/java/dev/banking/model/BankAccount.java index 745ede2..ca10ef0 100644 --- a/src/main/java/dev/banking/model/BankAccount.java +++ b/src/main/java/dev/banking/model/BankAccount.java @@ -1,18 +1,15 @@ package dev.banking.model; +import java.util.concurrent.locks.ReentrantLock; + public class BankAccount { private final int accountId; private long balance; - /* - * Students may introduce additional fields - * such as: - * - Lock / ReentrantLock - * - ReadWriteLock - * - Object monitor - * - etc. - */ + // explicit ReentrantLock associated with each individual account + // to provide independent lock contention per account + private final ReentrantLock lock = new ReentrantLock(); public BankAccount(int accountId, long initialBalance) { this.accountId = accountId; @@ -24,55 +21,75 @@ public class BankAccount { } /* - * 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 this.balance; + } finally { + lock.unlock(); + } } /* - * TODO: - * Increase balance atomically. - * - * Requirements: - * - Must not lose updates under concurrency + * increase balance atomically */ public void deposit(long amount) { - throw new UnsupportedOperationException("TODO: implement thread-safe deposit"); + if (amount <= 0) { + throw new IllegalArgumentException("Deposit amount must be positive."); + } + lock.lock(); + try { + this.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) + * Decrease balance atomically */ public void withdraw(long amount) { - throw new UnsupportedOperationException("TODO: implement thread-safe withdraw"); + if (amount <= 0) { + throw new IllegalArgumentException("Withdrawal amount must be positive."); + } + lock.lock(); + try { + this.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 (target == null) { + throw new IllegalArgumentException("Target account cannot be null."); + } + if (this.accountId == target.getAccountId()) { + throw new IllegalArgumentException("Cannot transfer to the same account."); + } + if (amount <= 0) { + throw new IllegalArgumentException("Transfer amount must be positive."); + } + + // Establish an absolute ordering to acquire locks + BankAccount firstLock = this.accountId < target.getAccountId() ? this : target; + BankAccount secondLock = this.accountId < target.getAccountId() ? target : this; + + firstLock.lock.lock(); + try { + secondLock.lock.lock(); + try { + // Perform the atomic transfer operations + this.withdraw(amount); + target.deposit(amount); + } finally { + secondLock.lock.unlock(); + } + } finally { + firstLock.lock.unlock(); + } } } \ No newline at end of file -- 2.54.0