import java.time.Instant;
import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

class BankAccount {
    private int balance = 0;

    synchronized void deposit(int amount) {
        balance += amount;
    }

    void withdraw(int amount) throws Exception {
        synchronized (this) {
            if (amount > balance) {
                throw new Exception("Withdraw too large");
            }
            balance -= amount;
        }
    }


    void transferTo(int amount, BankAccount a) throws Exception {
        synchronized (hashCode() < a.hashCode() ? this : a) {
            synchronized (hashCode() < a.hashCode() ? a : this) {
                this.withdraw(amount);
                a.deposit(amount);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        BankAccount account = new BankAccount();
        BankAccount account2 = new BankAccount();
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        account.deposit(10_000_000);
        account2.deposit(10_000_000);

        timedRun(() -> {
            for (int i=0; i<4; ++i) {
                executorService.execute(() -> {
                    for (int j=0; j<1_000_000; ++j) {
                        try {
                            account.transferTo(1, account2);
                            account2.transferTo(1, account);
                        } catch (Exception e) {}
                    }
                });
            }
            executorService.shutdown();
            try {
                executorService.awaitTermination(1, TimeUnit.MINUTES);
            } catch (Exception e) {}
        });

        System.out.println(account.balance);
        System.out.println(account2.balance);
    }

    static void timedRun(Runnable function) throws Exception {
        Instant start = Instant.now();
        function.run();
        System.out.println("Time: " + Duration.between(start, Instant.now()).toMillis() + "ms");
    }
}