package ar.com.javacuriosities.threads.liveness;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* Un LiveLock es similar a un Deadlock en sentido de que dos o mas threads se están bloqueando entre si, pero con un Livelock cada thread esta esperando "activamente",
* intentando resolver su problema, como por ejemplo librando sus locks y reintentando su tarea
*/
public class LiveLock {
public static void main(String[] args) {
BankAccount account1 = new BankAccount(1, 500d);
BankAccount account2 = new BankAccount(2, 500d);
Thread transfer1 = new Thread(new TransferTask(account1, account2, 10d), "TransferTask-1");
transfer1.start();
Thread transfer2 = new Thread(new TransferTask(account2, account1, 10d), "TransferTask-2");
transfer2.start();
}
private static final class TransferTask implements Runnable {
private double amount;
private BankAccount from;
private BankAccount to;
public TransferTask(BankAccount from, BankAccount to, double amount) {
this.from = from;
this.to = to;
this.amount = amount;
}
public void run() {
while (!from.tryTransfer(to, amount)) {
System.out.println(from + " is waiting for: " + to);
continue;
}
System.out.printf(Thread.currentThread().getName() + " Finalizada");
}
}
private static final class BankAccount {
private int id;
private double balance;
private Lock lock = new ReentrantLock();
public BankAccount(int id, double balance) {
this.id = id;
this.balance = balance;
}
public boolean withdraw(double amount) {
if (this.lock.tryLock()) {
// Simulamos algo de IO como por ejemplo acceso a una DB
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
// Log and Handle exception
e.printStackTrace();
}
balance -= amount;
return true;
}
return false;
}
public boolean deposit(double amount) {
if (this.lock.tryLock()) {
// Simulamos algo de IO como por ejemplo acceso a una DB
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
// Log and Handle exception
e.printStackTrace();
}
balance += amount;
return true;
}
return false;
}
public boolean tryTransfer(BankAccount destinationAccount, double amount) {
if (this.withdraw(amount)) {
if (destinationAccount.deposit(amount)) {
return true;
} else {
// La cuenta de destino esta ocupada así que hacemos un refund en la cuenta origen
this.deposit(amount);
}
}
return false;
}
@Override
public String toString() {
return "BankAccount [id=" + id + ", balance=" + balance + "]";
}
}
}