import java.util.concurrent.locks.ReentrantLock;

public class UrutanKunciKonsisten {
    private static class Akun {
        private final String nomor;
        private long saldo;
        private final ReentrantLock kunci = new ReentrantLock();

        Akun(String nomor, long saldoAwal) {
            this.nomor = nomor;
            this.saldo = saldoAwal;
        }
    }

    // Selalu kunci akun dengan nomor lebih kecil lebih dulu,
    // apa pun arah transfernya -- urutan global yang sama untuk semua thread.
    private static void transfer(Akun dari, Akun ke, long jumlah)
            throws InterruptedException {
        Akun pertama = dari.nomor.compareTo(ke.nomor) < 0 ? dari : ke;
        Akun kedua = (pertama == dari) ? ke : dari;

        pertama.kunci.lock();
        try {
            kedua.kunci.lock();
            try {
                Thread.sleep(50); // perpanjang jendela dua kunci terpegang bersama
                dari.saldo -= jumlah;
                ke.saldo += jumlah;
            } finally {
                kedua.kunci.unlock();
            }
        } finally {
            pertama.kunci.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Akun budi = new Akun("ACC-001", 1_000);
        Akun siti = new Akun("ACC-002", 500);

        // Dua transfer berlawanan arah pada saat bersamaan.
        Thread transferBudi = new Thread(() -> {
            try {
                transfer(budi, siti, 100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "transfer-Budi");
        Thread transferSiti = new Thread(() -> {
            try {
                transfer(siti, budi, 50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "transfer-Siti");

        transferBudi.start();
        transferSiti.start();
        transferBudi.join();
        transferSiti.join();

        System.out.println("program selesai tanpa kebuntuan");
        System.out.println("saldo ACC-001: " + budi.saldo);
        System.out.println("saldo ACC-002: " + siti.saldo);
    }
}
