From 39b3b5b46618f29ed1fef853a101ed94c167b42c Mon Sep 17 00:00:00 2001 From: "amirM.t" Date: Thu, 11 Jun 2026 23:35:26 +0330 Subject: [PATCH] feat : implement BankAccount methods + Solutions.md file (including bonus question) --- Solutions.md | 148 ++++++++++++++++++ .../java/dev/banking/model/BankAccount.java | 116 +++++++++----- 2 files changed, 228 insertions(+), 36 deletions(-) create mode 100644 Solutions.md diff --git a/Solutions.md b/Solutions.md new file mode 100644 index 0000000..78fdac9 --- /dev/null +++ b/Solutions.md @@ -0,0 +1,148 @@ +1 - What are atomic variables? + +Atomic variables are variables that support thread-safe operations without using explicit locks. + +They ensure that operations such as incrementing, decrementing, or updating a value are performed as a single indivisible action. + +Difference from ordinary variables: + +Ordinary variable: Multiple threads can modify it simultaneously, causing race conditions. +Atomic variable: Operations are performed atomically, preventing race conditions for single-variable updates. + +2 - Name at least four classes from the java.util.concurrent.atomic package that provide atomic operations for different data types. + +AtomicInteger +AtomicLong +AtomicBoolean +AtomicReference + +Typical use case for AtomicInteger: +AtomicInteger is commonly used as a thread-safe counter when multiple threads need to increment a shared value concurrently. + +3 - Compare locks with atomic variables. + +atomic variables: + +Advantages: + +Faster than locks for simple operations. +Non-blocking. +Lower overhead. + +Best for: + +Counters. +Flags. +Single-variable updates. +Locks + +locks: + +Advantages: +Can protect multiple variables together. +Support complex critical sections. + +Best for: + +Operations involving multiple shared objects. +Complex business logic requiring mutual exclusion. + +Summary: + +Use atomic variables for simple thread-safe updates. +Use locks when multiple operations must be performed as one atomic unit. + +Bonus Task + +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 numThreads = 10; + int incrementsPerThread = 100000; + + Thread[] threads = new Thread[numThreads]; + + for (int i = 0; i < numThreads; i++) { + threads[i] = new Thread(() -> { + for (int j = 0; j < incrementsPerThread; j++) { + normalCounter++; + atomicCounter.incrementAndGet(); + } + }); + + threads[i].start(); + } + + for (Thread thread : threads) { + thread.join(); + } + + System.out.println("Normal Counter: " + normalCounter); + System.out.println("Atomic Counter: " + atomicCounter.get()); + } +} + +Expected Result: + +Normal Counter: 823451 (varies) +Atomic Counter: 1000000 + +The normal counter may be incorrect because multiple threads overwrite each other's updates. The atomic counter always produces the correct result. + +4 - A program is completely free of race conditions but still performs poorly under high contention. +Explain how this situation can occur. + +A program can be completely correct yet still perform poorly because threads spend too much time waiting for shared resources. + +Three factors that limit scalability: + +Lock Contention +Many threads compete for the same lock and must wait. + +Synchronization Overhead +Acquiring and releasing locks consumes CPU time. + +Thread Blocking +Threads frequently pause while waiting for locks or resources, reducing parallelism. + +Correctness is guaranteed, but throughput decreases as contention increases. + +5 - Many concurrent systems experience performance degradation as the number of threads increases. +Explain why adding more threads does not always improve performance. + +The CPU must save and restore thread state when switching between threads. Too many threads increase this overhead. + +Contention +More threads compete for the same locks and resources, causing waiting. + +Cache Coherence +When multiple CPUs modify shared data, cache contents must be synchronized, creating extra communication overhead. + +Synchronization Overhead +Locks, atomic operations, and coordination mechanisms consume time and reduce the benefits of parallel execution. + +Conclusion: After a certain point, adding more threads increases overhead more than useful work, causing performance to stagnate or even decrease. + +6 - Deadlocks often only appear in production, not during testing.Explain why this might happen from a thread-scheduling perspective. + +Deadlocks depend on the exact timing and scheduling of threads. +During testing, threads may execute in a favorable order and never enter the deadlock state. In production, different workloads, hardware, and timing conditions can cause the problematic scheduling sequence to occur. + +Two ways to expose deadlocks during testing + +1. Increase concurrency +Run with many threads.execute tests repeatedly under heavy load. + + +2. Introduce timing variations +Add random delays (Thread.sleep()). +Use stress-testing tools to force different thread interleavings. + +These techniques increase the chance that threads acquire locks in a problematic order, revealing deadlocks before deployment. \ 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..7cabd7d 100644 --- a/src/main/java/dev/banking/model/BankAccount.java +++ b/src/main/java/dev/banking/model/BankAccount.java @@ -1,5 +1,8 @@ package dev.banking.model; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + public class BankAccount { private final int accountId; @@ -13,6 +16,7 @@ public class BankAccount { * - Object monitor * - etc. */ + private final ReadWriteLock lock = new ReentrantReadWriteLock(); public BankAccount(int accountId, long initialBalance) { this.accountId = accountId; @@ -23,56 +27,96 @@ 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 + /** + * Thread-safe balance read. */ public long getBalance() { - throw new UnsupportedOperationException("TODO: implement thread-safe balance read"); + + lock.readLock().lock(); + + try + { + return balance; + } + finally + { + lock.readLock().unlock(); + } } - /* - * TODO: - * Increase balance atomically. - * - * Requirements: - * - Must not lose updates under concurrency + /** + * Thread-safe deposit. */ public void deposit(long amount) { - throw new UnsupportedOperationException("TODO: implement thread-safe deposit"); + + lock.writeLock().lock(); + + try + { + balance += amount; + } + finally + { + lock.writeLock().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) + /** + * Thread-safe withdrawal. */ public void withdraw(long amount) { - throw new UnsupportedOperationException("TODO: implement thread-safe withdraw"); + + lock.writeLock().lock(); + + try + { + balance -= amount; + } + finally + { + lock.writeLock().unlock(); + } } - /* - * TODO: - * Transfer money between two accounts atomically. + /** + * Atomic deadlock-free transfer. * - * 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 + * Lock ordering rule: + * Always acquire the lock of the account with the + * smaller accountId first. */ 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 == target) + return; + + BankAccount first = this.accountId < target.accountId ? this : target; + + BankAccount second = this.accountId < target.accountId ? target : this; + + first.lock.writeLock().lock(); + + try + { + second.lock.writeLock().lock(); + + try + { + this.balance -= amount; + target.balance += amount; + } + finally + { + second.lock.writeLock().unlock(); + } + + } + finally + { + first.lock.writeLock().unlock(); + } } } \ No newline at end of file