import java.util.concurrent.locks.*;
import java.util.Random;
import java.util.concurrent.TimeUnit;

// Account class which had a deposit and withdraw method. The withdraw method allows the calling thread to timeout, by calling the awaitNanos() method with some parameter. This method, if interrupted, returns to the caller with the time remaining in the original timeout. If the condition that withdraw is waiting for (balance >= amount) is not yet true then the thread gives up if timeout is exhausted, or calls awaitNanos again for the remaining period of the timeout (until someone wakes it up).

class Account {
  int id;
  double balance;
  Lock lock;
  Condition q;
  
  public Account(int id, double balance) {
    this.id = id;
    this.balance = balance;
    lock = new ReentrantLock();
    q = lock.newCondition();
  }
  
  public double getBalance() {
    return balance;
  }
  
  public boolean deposit(double amount) {
    lock.lock();
    try {
      balance += amount;  
      return true;    
    } finally {
      lock.unlock();
    }
  }
  
  public boolean withdraw(int tid, double amount, long timeout) {
    lock.lock();
    long nanos = timeout;
    try {
      while (balance < amount) {
        if (nanos <= 0L) 
          return false;
        System.out.println("Thread " + tid + " stuck trying to withdraw " + amount + " from account " + id);
        nanos = q.awaitNanos(nanos);  
      }
      balance -= amount;
      System.out.println("Thread " + tid + " successfully withdrew " + amount + " from account " + id); 
      return true;
    } catch (InterruptedException ie) {
      return false;
    } finally {
      lock.unlock();
    }
  }
  
}

// The bank has multiple accounts and can transfer money between any two (in parallel threads). Each transfer is implemented as a withdraw followed by a deposit. The withdraw method waits for a specific time for the balance to become sufficient, and gives up at the end of the time period. Deposit is always successful. There is synchronization via locks and conditions at the level of the individual accounts.

public class Bank {
  int num_accts;
  Account [] accounts;

  public Bank(int num_accts) {
    Random r = new Random();
    this.num_accts = num_accts;
    accounts = new Account[num_accts];
    for (int i = 0; i < num_accts; i++) {
      accounts[i] = new Account(i, r.nextDouble(2000.00));
    }
    System.out.println("Creating bank with accounts as below:");
    for (int i = 0; i < num_accts; i++) 
      System.out.println("Initial balance in account[" + i + "]: " + accounts[i].getBalance());
  }
    
  boolean transfer(int id, double amount, int source, int target, long timeout) {
    if (source == target) return false;
    if (!accounts[source].withdraw(id, amount, timeout)) {
      System.out.println("Thread " + id + " timed out trying to transfer " + amount + " from account[" + source + "] to account[" + target + "]");
      return false;
    }
    accounts[target].deposit(amount);
    System.out.println("Thread " + id + " transferred " + amount + " from account[" + source + "] to account[" + target + "]");
    return true;
  }
}

