From 96532c56da34e1f169b08bd89c9cdbadd32bf8d9 Mon Sep 17 00:00:00 2001 From: Saeed Jamali Date: Sat, 13 Jun 2026 16:46:39 +0330 Subject: [PATCH] Implement all TODOs --- .idea/.gitignore | 10 +++ .idea/compiler.xml | 13 +++ .idea/encodings.xml | 7 ++ .idea/jarRepositories.xml | 20 +++++ .idea/misc.xml | 12 +++ .idea/vcs.xml | 6 ++ Answers.md | 48 ++++++++++ .../java/dev/banking/model/BankAccount.java | 87 ++++++++----------- .../dev/banking/model/DepositTransaction.java | 3 - .../java/dev/banking/model/Transaction.java | 3 - .../banking/model/TransferTransaction.java | 3 - .../banking/model/WithdrawTransaction.java | 3 - .../java/dev/banking/monitor/LiveMonitor.java | 4 - .../processor/TransactionProcessor.java | 17 +--- .../dev/banking/service/BankingSystem.java | 16 ---- 15 files changed, 154 insertions(+), 98 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..a9076af --- /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..5e4e294 --- /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/Answers.md b/Answers.md new file mode 100644 index 0000000..2c5a244 --- /dev/null +++ b/Answers.md @@ -0,0 +1,48 @@ +# Java Concurrency: Atomic Variables & Locks + +### 1. Atomic Variables +Atomic variables are tools that let us update a value safely when many threads are running. They ensure that an action (like adding 1) happens **all at once** without being interrupted. + +* **Difference:** Normal variables can lose data if two threads change them at the exact same time. Atomic variables use a "try-and-retry" method (CAS) to stay accurate without stopping other threads. + +### 2. Common Atomic Classes +* `AtomicInteger` +* `AtomicLong` +* `AtomicBoolean` +* `AtomicReference` + +**Use Case:** We use `AtomicInteger` for a **hit counter** on a website. It keeps the total count correct even if thousands of users click a button at the same second. + +### 3. Locks vs. Atomic Variables + +| Feature | Atomic Variables | Locks (`synchronized`) | +| :--- | :--- | :--- | +| **Speed** | Very Fast | Slower | +| **Thread Behavior** | Keep running (no waiting) | Stop and wait their turn | +| **Best For** | Single numbers or flags | Groups of variables or big tasks | + +* **We use Atomic Variables when:** We only need to change one value quickly and want to avoid the slowdown of locking. +* **We use Locks when:** We need to update **multiple related values** together or when the work takes a long time (like saving a file). + +### 4. Poor Performance without Race Conditions +Even if our code is "correct" (no data is lost), it can still be slow if many threads fight for the same resources. This is called **high contention**. + +**Factors that limit scaling:** +1. **Lock Contention:** Many threads wait in a long line for one lock, so only one thread actually works at a time. +2. **Context Switching:** The CPU spends more time "swapping" threads in and out than actually running our code. +3. **Memory Bottlenecks:** Threads might be waiting for data to move between the main memory and the CPU. + +### 5. Why More Threads Can Slow Us Down +Adding more threads has a "cost" that eventually outweighs the benefits. + +- **Context Switching:** Every time the CPU moves from one thread to another, it has to save and load data. This wastes time. +- **Contention:** If we have 100 threads but only 1 resource, 99 threads are sitting idle, which wastes memory. +- **Cache Coherence:** When one thread changes data, the CPU must tell all other cores to update their private "caches." This constant communication slows down the whole system. +- **Synchronization Overhead:** The tools we use to stay safe (like locks or atomic signals) require extra CPU work to manage. + +### 6. Deadlocks in Production vs. Testing +Deadlocks depend on **timing**. In testing, threads might always run in a "safe" order because the computer is less busy. In production, unexpected delays or high traffic can cause threads to grab locks in the "wrong" order, causing a freeze. + +**Strategies to find deadlocks during testing:** +1. **Thread Fuzzing:** We can add random, tiny delays (like `Thread.sleep()`) in our code during testing. This forces different timings and helps expose hidden deadlocks. +2. **Stress Testing:** We can run the program with a much higher number of threads and data than we expect. This makes it more likely that the rare "wrong timing" will happen. \ 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..65b5f23 100644 --- a/src/main/java/dev/banking/model/BankAccount.java +++ b/src/main/java/dev/banking/model/BankAccount.java @@ -1,18 +1,13 @@ 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; - - /* - * Students may introduce additional fields - * such as: - * - Lock / ReentrantLock - * - ReadWriteLock - * - Object monitor - * - etc. - */ + private final Lock lock = new ReentrantLock(); public BankAccount(int accountId, long initialBalance) { this.accountId = accountId; @@ -23,56 +18,48 @@ 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"); + BankAccount first = this.accountId < target.accountId ? this : target; + BankAccount second = this.accountId < target.accountId ? target : this; + + first.lock.lock(); + try { + second.lock.lock(); + try { + this.withdraw(amount); + target.deposit(amount); + } finally { + second.lock.unlock(); + } + } finally { + first.lock.unlock(); + } } } \ No newline at end of file diff --git a/src/main/java/dev/banking/model/DepositTransaction.java b/src/main/java/dev/banking/model/DepositTransaction.java index f4798d2..19869ee 100644 --- a/src/main/java/dev/banking/model/DepositTransaction.java +++ b/src/main/java/dev/banking/model/DepositTransaction.java @@ -1,8 +1,5 @@ package dev.banking.model; -/** - * Represents a deposit operation. - */ public final class DepositTransaction extends Transaction { private final int accountId; diff --git a/src/main/java/dev/banking/model/Transaction.java b/src/main/java/dev/banking/model/Transaction.java index 5d53921..73b3f88 100644 --- a/src/main/java/dev/banking/model/Transaction.java +++ b/src/main/java/dev/banking/model/Transaction.java @@ -1,8 +1,5 @@ package dev.banking.model; -/** - * Base class for all transaction types. - */ public abstract class Transaction { private final int amount; diff --git a/src/main/java/dev/banking/model/TransferTransaction.java b/src/main/java/dev/banking/model/TransferTransaction.java index 89d3a57..77eef48 100644 --- a/src/main/java/dev/banking/model/TransferTransaction.java +++ b/src/main/java/dev/banking/model/TransferTransaction.java @@ -1,8 +1,5 @@ package dev.banking.model; -/** - * Represents a transfer operation between two accounts. - */ public final class TransferTransaction extends Transaction { diff --git a/src/main/java/dev/banking/model/WithdrawTransaction.java b/src/main/java/dev/banking/model/WithdrawTransaction.java index ba1ebd2..417f503 100644 --- a/src/main/java/dev/banking/model/WithdrawTransaction.java +++ b/src/main/java/dev/banking/model/WithdrawTransaction.java @@ -1,8 +1,5 @@ package dev.banking.model; -/** - * Represents a withdrawal operation. - */ public final class WithdrawTransaction extends Transaction { private final int accountId; diff --git a/src/main/java/dev/banking/monitor/LiveMonitor.java b/src/main/java/dev/banking/monitor/LiveMonitor.java index a625199..caed995 100644 --- a/src/main/java/dev/banking/monitor/LiveMonitor.java +++ b/src/main/java/dev/banking/monitor/LiveMonitor.java @@ -1,7 +1,6 @@ package dev.banking.monitor; import dev.banking.model.BankAccount; - import java.util.Collection; public class LiveMonitor { @@ -9,15 +8,12 @@ public class LiveMonitor { public void update( Collection accounts ) { - for (BankAccount account : accounts) { - System.out.printf( "Account %d -> %d%n", account.getAccountId(), account.getBalance() ); - } } } \ No newline at end of file diff --git a/src/main/java/dev/banking/processor/TransactionProcessor.java b/src/main/java/dev/banking/processor/TransactionProcessor.java index 58d7e36..9e934d5 100644 --- a/src/main/java/dev/banking/processor/TransactionProcessor.java +++ b/src/main/java/dev/banking/processor/TransactionProcessor.java @@ -1,7 +1,6 @@ package dev.banking.processor; import dev.banking.model.*; - import java.util.Map; public class TransactionProcessor { @@ -15,33 +14,19 @@ public class TransactionProcessor { } public void process(Transaction tx) { - if (tx instanceof DepositTransaction deposit) { - BankAccount account = accounts.get(deposit.getAccountId()); - account.deposit(deposit.getAmount()); } - else if (tx instanceof WithdrawTransaction withdraw) { - BankAccount account = accounts.get(withdraw.getAccountId()); - account.withdraw(withdraw.getAmount()); } - else if (tx instanceof TransferTransaction transfer) { - BankAccount source = accounts.get(transfer.getSourceAccountId()); - BankAccount target = accounts.get(transfer.getTargetAccountId()); - - source.transfer( - target, - transfer.getAmount() - ); + source.transfer(target, transfer.getAmount()); } - else { throw new IllegalArgumentException( "Unknown transaction type: " + tx.getClass() diff --git a/src/main/java/dev/banking/service/BankingSystem.java b/src/main/java/dev/banking/service/BankingSystem.java index 255e01c..2b09b0d 100644 --- a/src/main/java/dev/banking/service/BankingSystem.java +++ b/src/main/java/dev/banking/service/BankingSystem.java @@ -2,22 +2,9 @@ package dev.banking.service; import dev.banking.model.*; import dev.banking.processor.TransactionProcessor; - import java.util.List; import java.util.concurrent.ExecutorService; -/** - * Dispatches a list of transactions to a shared ExecutorService - * for concurrent (asynchronous) processing. - * - * Each transaction is submitted as an independent task and may - * be executed in parallel depending on thread availability. - * - * No ordering guarantees are provided between transactions. - * - * Lifecycle management of the ExecutorService (creation, - * shutdown, termination) is handled outside this class. - */ public class BankingSystem { private final ExecutorService executor; @@ -34,13 +21,10 @@ public class BankingSystem { public void processTransactions( List transactions ) { - for (Transaction tx : transactions) { - executor.submit(() -> { processor.process(tx); }); - } } } \ No newline at end of file