import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class Bank { // Class fields private final Map accounts; private final Lock bankLock; /** * Constructor initializes all fields */ public Bank() { // Note you would also need to make sure HashMap access is thread safe too! // Thankfully Java has built-in concurrent versions of their data structures // to not cause issues with concurrent access to data structures. this.accounts = new ConcurrentHashMap<>(); this.bankLock = new ReentrantLock(); } /** * Makes an account with the given ID (assumes unique) and initial * account balance */ public void createAccount(String accountId, double initialBalance) { accounts.put(accountId, new Account(accountId, initialBalance)); } /** * Thread-safe withdraw operation * @param accountId The account to withdraw from * @param amount The amount to withdraw * @return true if withdrawal was successful, false otherwise */ public boolean withdraw(String accountId, double amount) { if (amount <= 0) { throw new IllegalArgumentException("Withdrawal amount must be positive"); } // Get the account Account account = accounts.get(accountId); if (account == null) { return false; } // Lock the specific account for the withdrawal operation account.lock.lock(); try { // Check if there are sufficient funds if (account.balance >= amount) { account.balance -= amount; return true; } return false; } finally { // Always release the lock in a finally block // to avoid exceptions causing the lock to be held forever account.lock.unlock(); } } /** * Thread-safe transfer between accounts * @param fromAccountId Source account * @param toAccountId Destination account * @param amount Amount to transfer * @return true if transfer was successful, false otherwise */ public boolean transfer(String fromAccountId, String toAccountId, double amount) { if (amount <= 0) { throw new IllegalArgumentException("Transfer amount must be positive"); } Account fromAccount = accounts.get(fromAccountId); Account toAccount = accounts.get(toAccountId); if (fromAccount == null || toAccount == null) { return false; } // Always acquire locks in the same order to prevent deadlocks // Use account IDs to determine locking order Lock firstLock, secondLock; boolean isFromFirst = fromAccount.accountId.compareTo(toAccount.accountId) < 0; if (isFromFirst) { firstLock = fromAccount.lock; secondLock = toAccount.lock; } else { firstLock = toAccount.lock; secondLock = fromAccount.lock; } // Acquire both locks firstLock.lock(); try { secondLock.lock(); try { // Check sufficient funds if (fromAccount.balance >= amount) { fromAccount.balance -= amount; toAccount.balance += amount; return true; } return false; } finally { secondLock.unlock(); } } finally { firstLock.unlock(); } } /** * Gets the current balance of an account in a thread-safe manner. * This method acquires a lock on the account to ensure a consistent read. * * @param accountId The account to check * @return The current balance of the account * @throws IllegalArgumentException if the account does not exist */ public double getBalance(String accountId) { Account account = accounts.get(accountId); if (account == null) { throw new IllegalArgumentException("Account does not exist"); } account.lock.lock(); try { return account.balance; } finally { account.lock.unlock(); } } // Inner class representing an account private static class Account { private final String accountId; private double balance; private final Lock lock; /** * Constructor initializes all fields * @param accountId The account identifier * @param initialBalance The starting balance */ public Account(String accountId, double initialBalance) { this.accountId = accountId; this.balance = initialBalance; this.lock = new ReentrantLock(); } } }