diff --git a/src/main/java/dev/banking/model/BankAccount.java b/src/main/java/dev/banking/model/BankAccount.java index 745ede2..b0b5f91 100644 --- a/src/main/java/dev/banking/model/BankAccount.java +++ b/src/main/java/dev/banking/model/BankAccount.java @@ -1,18 +1,16 @@ package dev.banking.model; +import java.util.concurrent.TimeUnit; +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. - */ + public final ReentrantLock lock = new ReentrantLock(); + private final Condition sufficientFundsCondition = lock.newCondition(); public BankAccount(int accountId, long initialBalance) { this.accountId = accountId; @@ -23,56 +21,88 @@ 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"); + if (amount <= 0) return; + + lock.lock(); + try { + balance += amount; + //Bonus Task + sufficientFundsCondition.signalAll(); + } 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"); + if (amount <= 0) return; + + lock.lock(); + try { + //Bonus Task + while (balance < amount) { + try { + boolean receivedSignal = sufficientFundsCondition.await(50, TimeUnit.MILLISECONDS); + if (!receivedSignal && balance < amount) { + return; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + 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"); + if (target == null || this == target || amount <= 0) return; + + BankAccount firstAccount = this.accountId < target.getAccountId() ? this : target; + BankAccount secondAccount = firstAccount == this ? target : this; + + firstAccount.lock.lock(); + secondAccount.lock.lock(); + + try { + //Bonus Task + while (this.balance < amount) { + try { + secondAccount.lock.unlock(); + + boolean receivedSignal = this.sufficientFundsCondition.await(50, TimeUnit.MILLISECONDS); + + secondAccount.lock.lock(); + + if (!receivedSignal && this.balance < amount) { + return; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + + this.balance -= amount; + target.balance += amount; + + //Bonus Task + target.sufficientFundsCondition.signalAll(); + + } finally { + secondAccount.lock.unlock(); + firstAccount.lock.unlock(); + } } } \ No newline at end of file