From a2f3b1e1acfe8436629e85256dae9b0f5edc7482 Mon Sep 17 00:00:00 2001 From: ava Date: Sun, 14 Jun 2026 17:54:43 +0330 Subject: [PATCH] implementing codes in 3 files --- .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 | 118 ++++++++++++++++++ .../java/dev/banking/model/BankAccount.java | 46 +++++-- 8 files changed, 226 insertions(+), 7 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..f24c79d --- /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..84f5d7e --- /dev/null +++ b/Answers.md @@ -0,0 +1,118 @@ +# Answers + +## Question 1 — Atomic Variables + +Atomic variables are variables that support thread-safe operations without using locks. +When multiple threads try to do something like `i++` on a normal variable, this operation +actually has three steps: read, add, write. Another thread can jump in between these steps +and cause wrong results. Atomic variables do this whole thing in one unbreakable step using +a CPU instruction called CAS (Compare And Swap), so no thread can interrupt in the middle. + +## Question 2 — Atomic Classes + +Four classes from java.util.concurrent.atomic: +- AtomicInteger +- AtomicLong +- AtomicBoolean +- AtomicReference + +Use case for AtomicInteger: +Counting how many transactions have been processed across multiple threads. +Instead of putting a lock around a simple counter, we can call +`atomicCounter.incrementAndGet()` which is faster and doesn't block threads. + +## Question 3 — Locks vs Atomic Variables + +Atomic variables are better when we only need to update a single variable safely, +like a counter or a flag. They are faster because they don't block other threads. + +Locks are better when we need to update multiple variables together and all of them +must change as one unit. For example in a bank transfer, we need to subtract from one +account and add to another — both must happen together, so we use locks. + +## Question 4 — Good Correctness but Bad Performance + +A program can be completely correct with no race conditions but still be slow because: + +1. Lock contention: if many threads want the same lock at the same time, they all have + to wait in line. Only one runs while the rest are stuck doing nothing. + +2. False sharing: two threads might be working on different variables, but those variables + are stored next to each other in CPU cache. When one thread changes its variable, the CPU + forces the other thread to reload its cache even though nothing it cares about changed. + +3. Coarse-grained locking: using one big lock for everything means even operations that + have nothing to do with each other have to wait. For example depositing into account A + and account B could happen at the same time, but a global lock prevents that. + +## Question 5 — More Threads Doesn't Always Mean Faster + +- Context switching: when there are more threads than CPU cores, the OS keeps switching + between them. Each switch takes time and does zero useful work. + +- Contention: more threads fighting over the same lock means longer wait times. + At some point adding threads just makes the queue longer, not the work faster. + +- Cache coherence: each CPU core has its own cache. When a shared variable changes, + all cores must update their copy. With many threads on many cores, this communication + becomes a bottleneck. + +- Synchronization overhead: every lock and unlock has a cost. With too many threads, + the time spent on synchronization can be more than the actual work being done. + +## Question 6 — Deadlocks in Production but Not in Testing + +Deadlocks need a very specific order of events to happen. Thread1 must lock A at exactly +the moment Thread2 locks B, and then both try to get each other's lock. In testing, +the system is usually under low load with few threads, so this exact timing almost never +occurs. In production with thousands of concurrent threads, the chances of hitting that +exact bad sequence become much higher. + +Two strategies to expose deadlocks during testing: + +1. Stress testing: run tests with a very large number of threads doing random transactions + at the same time. The more threads competing, the higher the chance of triggering the + exact bad interleaving that causes a deadlock. + +2. Inserting Thread.sleep() inside critical sections during tests: this artificially + slows down the thread right in the middle of acquiring locks, making it much more likely + that another thread will jump in and create the deadlock scenario. + +## Bonus — AtomicInteger vs Normal int + +```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 threadCount = 1000; + Thread[] threads = new Thread[threadCount]; + + for (int i = 0; i < threadCount; i++) { + threads[i] = new Thread(() -> { + for (int j = 0; j < 1000; j++) { + normalCounter++; + atomicCounter.incrementAndGet(); + } + }); + threads[i].start(); + } + + for (Thread t : threads) { + t.join(); + } + + System.out.println("Expected: " + (threadCount * 1000)); + System.out.println("Normal int result: " + normalCounter); + System.out.println("AtomicInteger result: " + atomicCounter.get()); + } +} +``` + +The normal int will show a wrong number because threads interfere with each other. +The AtomicInteger will always show the correct result of 1000000. \ 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..6eebc83 100644 --- a/src/main/java/dev/banking/model/BankAccount.java +++ b/src/main/java/dev/banking/model/BankAccount.java @@ -1,10 +1,10 @@ package dev.banking.model; - +import java.util.concurrent.locks.ReentrantLock; public class BankAccount { private final int accountId; private long balance; - + private final ReentrantLock lock = new ReentrantLock(); /* * Students may introduce additional fields * such as: @@ -22,7 +22,6 @@ public class BankAccount { public int getAccountId() { return accountId; } - /* * TODO: * Return the current balance in a thread-safe way. @@ -32,7 +31,13 @@ public class BankAccount { * - 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(); + } + } /* @@ -42,8 +47,14 @@ public class BankAccount { * 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(); + } } /* @@ -55,8 +66,14 @@ public class BankAccount { * - 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(); + } } /* @@ -72,7 +89,22 @@ public class BankAccount { * - 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.balance -= amount; + target.balance += amount; + } finally { + second.lock.unlock(); + } + } finally { + first.lock.unlock(); + } } } \ No newline at end of file