From 5fc538a623efa39015b6fef952ffd68715942073 Mon Sep 17 00:00:00 2001 From: Romina Date: Fri, 12 Jun 2026 23:53:54 +0330 Subject: [PATCH] Advanced Multithreading --- .idea/.gitignore | 10 + .idea/compiler.xml | 13 + .idea/encodings.xml | 7 + .idea/jarRepositories.xml | 20 ++ .idea/misc.xml | 12 + .idea/vcs.xml | 7 + Answers.md | 229 ++++++++++++++++++ .../java/dev/banking/model/BankAccount.java | 125 +++++----- 8 files changed, 365 insertions(+), 58 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 Answers.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..4158879 --- /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..8306744 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Answers.md b/Answers.md new file mode 100644 index 0000000..9cdba0f --- /dev/null +++ b/Answers.md @@ -0,0 +1,229 @@ +# Ninth Assignment — Answers + +--- + +### ⚛️ Atomic Variables & Synchronization + +--- + +### **1 - What are atomic variables?** + +Atomic variables are variables that support **lock-free, thread-safe operations**. This means operations on them (such as increment, compare-and-set, etc.) are performed as a single indivisible step. + +Their main purpose is to **prevent race conditions** when multiple threads access and modify shared data concurrently. + +#### Difference from ordinary variables: + +* **Ordinary variables (non-atomic):** + + * Operations like `x++` are NOT atomic. + * They consist of multiple steps: read → modify → write. + * This can lead to race conditions. + +* **Atomic variables:** + + * Provide built-in thread-safe operations. + * Use low-level CPU instructions (like CAS – Compare-And-Swap). + * No need for explicit synchronization (like `synchronized` or locks). + +--- + +### **2 - Atomic classes in `java.util.concurrent.atomic`** + +Examples of atomic classes: + +* `AtomicInteger` +* `AtomicLong` +* `AtomicBoolean` +* `AtomicReference` + +#### Example use case: `AtomicInteger` + +`AtomicInteger` is commonly used as a **thread-safe counter**. + +Example: + +* Counting number of requests in a web server +* Tracking successful operations across multiple threads + +It avoids race conditions without using locks, improving performance under moderate contention. + +--- + +### **3 - Locks vs Atomic Variables** + +#### Atomic Variables: + +**Advantages:** + +* Faster (lock-free) +* No blocking +* Simple for single-variable operations + +**Limitations:** + +* Only suitable for **simple operations** +* Not useful for multi-step or multi-variable logic + +--- + +#### Locks (`synchronized`, `ReentrantLock`): + +**Advantages:** + +* Can protect **complex operations** +* Allow coordination across multiple variables +* Support conditions and waiting + +**Disadvantages:** + +* Slower due to blocking +* Can cause deadlocks if misused + +--- + +#### When to use which? + +* Use **Atomic Variables** when: + + * You have simple operations (e.g., increment counter) + * No need for coordination between multiple variables + +* Use **Locks** when: + + * Multiple shared variables are involved + * You need atomic multi-step operations + * You need waiting/notification mechanisms + +--- + +### 🎯 Bonus Task — Example Program + +```java +import java.util.concurrent.atomic.AtomicInteger; + +public class AtomicDemo { + static int normalCounter = 0; + static AtomicInteger atomicCounter = new AtomicInteger(0); + + public static void main(String[] args) throws InterruptedException { + int threads = 10; + Thread[] workers = new Thread[threads]; + + for (int i = 0; i < threads; i++) { + workers[i] = new Thread(() -> { + for (int j = 0; j < 1000; j++) { + normalCounter++; // NOT thread-safe + atomicCounter.incrementAndGet(); // thread-safe + } + }); + workers[i].start(); + } + + for (Thread t : workers) { + t.join(); + } + + System.out.println("Normal Counter: " + normalCounter); + System.out.println("Atomic Counter: " + atomicCounter.get()); + } +} +``` + +Expected result: + +* `atomicCounter` will always be correct (10000) +* `normalCounter` may be less due to race conditions + +--- + +### 🔒 Locks & Concurrent Design + +--- + +### **4 - Race-free but poor performance** + +A program can be completely correct (no race conditions) but still perform poorly due to **high contention and synchronization overhead**. + +#### Reasons: + +1. **High contention** + + * Many threads compete for the same lock + * Threads spend time waiting instead of doing work + +2. **Excessive synchronization** + + * Overuse of locks reduces parallelism + * Even independent operations get blocked + +3. **Lock granularity issues** + + * Coarse-grained locks (one big lock) + * Prevent multiple threads from working in parallel + +--- + +### **5 - Why more threads ≠ better performance** + +Adding more threads can hurt performance due to: + +#### 1. Context Switching + +* CPU switches between threads +* This has overhead and wastes time + +#### 2. Contention + +* Threads compete for shared resources (locks, memory) +* More threads → more waiting + +#### 3. Cache Coherence + +* CPUs maintain consistency of cached data +* Frequent updates cause cache invalidation +* Slows down execution + +#### 4. Synchronization Overhead + +* Locks and coordination add extra cost +* More threads → more synchronization + +--- + +### ⚠️ Deadlocks + +--- + +### **6 - Why deadlocks appear in production** + +Deadlocks depend on **thread scheduling**, which is: + +* Non-deterministic +* Timing-dependent + +In testing: + +* Fewer threads +* Predictable execution + +In production: + +* High concurrency +* Different timing → circular waits may occur + +--- + +#### Strategies to expose deadlocks: + +1. **Stress testing** + + * Run with many threads + * Increase contention + * Repeat tests multiple times + +2. **Introduce artificial delays** + + * Add `sleep()` between lock acquisitions + * Makes timing issues more visible + * Increases probability of deadlock \ No newline at end of file diff --git a/src/main/java/dev/banking/model/BankAccount.java b/src/main/java/dev/banking/model/BankAccount.java index 745ede2..c2a85c2 100644 --- a/src/main/java/dev/banking/model/BankAccount.java +++ b/src/main/java/dev/banking/model/BankAccount.java @@ -1,78 +1,87 @@ package dev.banking.model; -public class BankAccount { +import java.util.concurrent.locks.Condition; +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. - */ + private final ReentrantLock lock = new ReentrantLock(); + private final Condition sufficientFunds = lock.newCondition(); - public BankAccount(int accountId, long initialBalance) { + public BankAccount(int accountId, long initialBalance) + { this.accountId = accountId; this.balance = initialBalance; } - public int getAccountId() { - return accountId; + public int getAccountId() {return accountId;} + + public long getBalance() + { + lock.lock(); + try {return balance;} + finally {lock.unlock();} } - /* - * 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"); + public void deposit(long amount) + { + lock.lock(); + try + { + balance += amount; + sufficientFunds.signalAll(); // wake waiting threads + } + 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"); + public void withdraw(long amount) + { + lock.lock(); + try + { + while (balance < amount) {sufficientFunds.await();} + balance -= amount; + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + 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"); - } + public void transfer(BankAccount target, long amount) + { + if (this == target) return; - /* - * 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"); + BankAccount first = this.accountId < target.accountId ? this : target; + BankAccount second = this.accountId < target.accountId ? target : this; + + first.lock.lock(); + second.lock.lock(); + + try + { + while (this.balance < amount) {this.sufficientFunds.await();} + + this.balance -= amount; + target.balance += amount; + + this.sufficientFunds.signalAll(); + target.sufficientFunds.signalAll(); + + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + finally + { + second.lock.unlock(); + first.lock.unlock(); + } } } \ No newline at end of file