From e4cee41a99eb92583be8b71e4c9ed7ab9eae5983 Mon Sep 17 00:00:00 2001 From: Reza Date: Fri, 12 Jun 2026 11:28:16 +0330 Subject: [PATCH] complete --- .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 | 39 +++++++++ src/main/java/dev/banking/AtomicDemo.java | 40 +++++++++ .../java/dev/banking/model/BankAccount.java | 87 ++++++++----------- 9 files changed, 181 insertions(+), 53 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 create mode 100644 src/main/java/dev/banking/AtomicDemo.java 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..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/Answers.md b/Answers.md new file mode 100644 index 0000000..0f4285f --- /dev/null +++ b/Answers.md @@ -0,0 +1,39 @@ +# Answers to Theoretical Questions + +### 1. Atomic Variables vs. Normal Variables +Atomic variables are special classes that allow us to perform operations (like increments or updates) as a single, indivisible step. + +The main difference is how they handle concurrency. With a normal variable (like `int`), an operation like `counter++` actually takes three separate steps: read the value, add one, and write it back. If multiple threads do this at the same time, they can interrupt each other and mess up the final value (a race condition). Atomic variables fix this; an operation like `atomicInt.incrementAndGet()` does all three steps in one uninterruptible motion, guaranteeing thread safety without needing explicit locks. + +### 2. Four Classes from `java.util.concurrent.atomic` +Four commonly used atomic classes are: +1. `AtomicInteger` +2. `AtomicLong` +3. `AtomicBoolean` +4. `AtomicReference` + +**Use Case:** Imagine we are building a web server and need to count incoming HTTP requests. Instead of using `synchronized` blocks which can slow things down, we can use an `AtomicInteger`. We simply call `incrementAndGet()` every time a request comes in. It safely and efficiently counts the requests even if thousands of threads hit it at the exact same millisecond. + +### 3. Locks vs. Atomic Variables +* **When to use Atomic Variables:** They are the best choice when we only need to update a *single* shared variable (like a simple counter or a flag). They are lightweight, fast, and don't block threads the way locks do. +* **When to use Locks:** We need locks when our critical section involves updating *multiple* variables at the same time, or when we are doing a complex multi-step operation that must be atomic as a whole (for example, transferring money from Account A to Account B). Atomic variables can't group multiple different variables into one atomic action. + +### 4. Poor Performance Under High Load (Without Race Conditions) +Even if a program is perfectly thread-safe and has no race conditions, its performance can tank under heavy load due to: +1. **Contention:** Too many threads waiting in line for the same lock. Only one thread gets to execute while the rest are blocked, effectively destroying the benefits of parallel processing. +2. **False Sharing:** When two completely unrelated variables end up sitting in the same CPU cache line. If one thread updates variable A, the CPU is forced to invalidate and sync the entire cache line across other cores that might just be reading variable B, causing massive overhead. +3. **Over-synchronization:** Locking large chunks of code (like adding `synchronized` to an entire large method) when only a tiny fraction of that code actually accesses shared data. + +### 5. Why Adding More Threads Doesn't Always Improve Performance +Adding threads has a physical cost for the OS and the CPU. Eventually, the overhead outweighs the benefits: +* **Context Switching:** The CPU has to constantly pause threads, save their current state, and load the state of the next thread. This takes time away from actual execution. +* **Contention:** More threads mean more competition for shared resources, memory, and locks, leading to longer wait times. +* **Cache Coherence:** Keeping CPU caches synchronized across multiple cores gets exponentially more expensive as more threads constantly modify shared data. +* **Synchronization Overhead:** The sheer act of acquiring and releasing locks takes time, which adds up quickly when hundreds of threads are involved. + +### 6. Deadlocks in Production vs. Testing +**Why they appear in production:** Testing environments are usually predictable, have fewer threads, and execute code in a clean order. Production environments are chaotic—they handle thousands of concurrent threads with unpredictable network lags and OS scheduling. Deadlocks usually require a very specific, "unlucky" timing of lock acquisitions, which naturally surfaces in the chaos of production but rarely in isolated tests. + +**Two ways to catch them in testing:** +1. **Stress Testing:** Bombard the application with a massive number of threads and run the test loops thousands of times. This brute-force approach increases the statistical probability of hitting that unlucky lock order. +2. **Inject Random Delays:** Place a `Thread.sleep(randomTime)` right between acquiring the first lock and trying to acquire the second lock. This simulates real-world OS/network delays and easily exposes a vulnerable locking order. \ No newline at end of file diff --git a/src/main/java/dev/banking/AtomicDemo.java b/src/main/java/dev/banking/AtomicDemo.java new file mode 100644 index 0000000..668a5ff --- /dev/null +++ b/src/main/java/dev/banking/AtomicDemo.java @@ -0,0 +1,40 @@ +package dev.banking; + +import java.util.concurrent.atomic.AtomicInteger; + +public class AtomicDemo { + private static int normalCounter = 0; + private static final AtomicInteger atomicCounter = new AtomicInteger(0); + + public static void main(String[] args) throws InterruptedException { + int threads = 10; + int increments = 1000; + + Thread[] threadList = new Thread[threads * 2]; + + for (int i = 0; i < threads; i++) { + threadList[i * 2] = new Thread(() -> { + for (int j = 0; j < increments; j++) { + normalCounter++; + } + }); + + threadList[i * 2 + 1] = new Thread(() -> { + for (int j = 0; j < increments; j++) { + atomicCounter.incrementAndGet(); + } + }); + + threadList[i * 2].start(); + threadList[i * 2 + 1].start(); + } + + for (Thread t : threadList) { + t.join(); + } + int expected = threads * increments; + System.out.println("Expected value: " + expected); + System.out.println("Normal int: " + normalCounter); + System.out.println("AtomicInteger: " + atomicCounter.get()); + } +} \ 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..42e5a84 100644 --- a/src/main/java/dev/banking/model/BankAccount.java +++ b/src/main/java/dev/banking/model/BankAccount.java @@ -5,15 +5,6 @@ public class BankAccount { private final int accountId; private long balance; - /* - * Students may introduce additional fields - * such as: - * - Lock / ReentrantLock - * - ReadWriteLock - * - Object monitor - * - etc. - */ - public BankAccount(int accountId, long initialBalance) { this.accountId = accountId; this.balance = initialBalance; @@ -23,56 +14,46 @@ 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"); + public synchronized long getBalance() { + return balance; } - /* - * 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 synchronized void deposit(long amount) { + balance += amount; + notifyAll(); } - /* - * 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 synchronized void withdraw(long amount) { + while (balance < amount) { + try { + wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + balance -= amount; } - /* - * 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 first = (this.accountId < target.accountId) ? this : target; + BankAccount second = (this.accountId < target.accountId) ? target : this; + + synchronized (first) { + synchronized (second) { + if (this.balance >= amount) { + this.balance -= amount; + target.balance += amount; + target.notifyAll(); + return; + } + } + this.withdraw(amount); + target.deposit(amount); + } } } \ No newline at end of file