import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

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

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

    // Transfer dua tahap tryLock dengan penarikan diri (back-off):
    // bila kunci kedua tidak diperoleh dalam 50 ms, kunci pertama dilepas
    // dan thread mundur selama jedaAtur milidetik sebelum mencoba lagi.
    private static void transfer(Akun dari, Akun ke, long jumlah, long jedaAtur)
            throws InterruptedException {
        while (true) {
            if (dari.kunci.tryLock(50, TimeUnit.MILLISECONDS)) {
                try {
                    if (ke.kunci.tryLock(50, TimeUnit.MILLISECONDS)) {
                        try {
                            dari.saldo -= jumlah;
                            ke.saldo += jumlah;
                            return; // transfer berhasil
                        } finally {
                            ke.kunci.unlock();
                        }
                    }
                } finally {
                    // Kunci kedua gagal diraih: lepas juga kunci pertama
                    // agar thread lawan bisa maju -- inilah inti pemulihan.
                    dari.kunci.unlock();
                }
            }
            Thread.sleep(jedaAtur); // mundur selangkah, lalu coba lagi
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Akun budi = new Akun(1_000);
        Akun siti = new Akun(500);

        // Back-off kedua thread sengaja berbeda (10 ms vs 25 ms)
        // agar keduanya tidak terus-menerus bertabrakan pada irama yang sama.
        Thread transferBudi = new Thread(() -> {
            try {
                transfer(budi, siti, 100, 10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "transfer-Budi");
        Thread transferSiti = new Thread(() -> {
            try {
                transfer(siti, budi, 50, 25);
            } 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 Budi: " + budi.saldo);
        System.out.println("saldo Siti: " + siti.saldo);
    }
}
