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

public class BackoffJitter {
    private static final Random acak = new Random(42); // seed tetap: urutan jeda terjamin

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

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

    // Dua tahap tryLock dengan jeda back-off ACAK (5-29 ms).
    // Keacakan memecah irama identik yang menjadi akar livelock:
    // jeda kedua thread hampir pasti berbeda, salah satu maju lebih dulu.
    private static void transfer(Akun dari, Akun ke, long jumlah)
            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;
                        } finally {
                            ke.kunci.unlock();
                        }
                    }
                } finally {
                    dari.kunci.unlock(); // gagal raih kunci kedua: lepas juga yang pertama
                }
            }
            Thread.sleep(5 + acak.nextInt(25)); // jeda acak 5-29 ms
        }
    }

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

        // Kedua thread memakai method yang sama dengan irama yang sama,
        // hanya jeda acak yang membedakan nasibnya.
        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("kedua transfer selesai");
        System.out.println("saldo Budi: " + budi.saldo);
        System.out.println("saldo Siti: " + siti.saldo);
    }
}
