package ar.com.javacuriosities.threads; /* * Cuando usamos sincronización es para asegurarnos la exclusion de multiples * objetos trabajando sobre los mismos datos. * * En nuestro ejemplo los datos serían la BankAccount y nos referimos al mismo dato * porque ambos Customer interactúan con la misma instancia. * Si ejecutamos este código y vemos los println veremos que la información no tiene mucho sentido * y es incorrecta y los resultados pueden ser distintos en base al orden de ejecución. * * Esto se conoce como Race Condition lo cual ocurre cuando dos o mas procesos acceden a un recurso compartido sin control, * de manera que el resultado combinado de este acceso depende del orden de llegada. * * Cada objeto tiene lo que se conoce como un lock/monitor/candado el cual podemos usar para controlar el acceso a recursos * compartidos la forma mas básica de esto es cuando mencionamos la keyword synchronized en la firma del método * lo cual va a generar que solo un thread a la vez pueda interactuar con ese método * * Existen varias formas de sincronización: * * 1- Sincronizando el recurso que estamos usando, esto lo podemos hacer con synchronized en la firma del método o * synchronized(this) lo cual es exactamente lo mismo, ponerla en la firma es simplemente azúcar sintáctico * * 2- Sincronizando por medio de un mutex (Nombre típico de la variable que viene de Mutual Exclusion), que se refiere * a usar del candado de ese objeto haciendo synchronized(mutex) * * 3- También podemos hacer sincronizados métodos que sean estáticos aunque ahí la diferencia radica que no se usa el candado * de la instancia sino de la clase */ public class Lesson08Synchronized { public static void main(String[] args) { BankAccount bankAccount = new BankAccount(); bankAccount.deposit(80); Customer customer01 = new Customer(bankAccount); Customer customer02 = new Customer(bankAccount); Thread withdraw01 = new WithdrawTask(customer01, 50); Thread withdraw02 = new WithdrawTask(customer02, 40); withdraw01.start(); withdraw02.start(); } private static final class BankAccount { private double balance = 0; /* * Si queremos que el ejemplo funcione de forma correcta solo cambiamos la firma de los métodos usando * la keyword synchronized */ // public synchronized void deposit(double amount) { public void deposit(double amount) { if (amount > 0) { balance = balance + amount; } } // public synchronized double withdraw(double amount) { public double withdraw(double amount) { System.out.println(Thread.currentThread().getName() + " - Balance before condition: " + balance); if (amount > 0 && balance >= amount) { sleep(); balance = balance - amount; System.out.println(Thread.currentThread().getName() + " - Balance after withdraw: " + balance); return balance; } else { throw new RuntimeException("Insufficient funds"); } } private void sleep() { try { Thread.sleep(1); } catch (InterruptedException e) { // Log and Handle exception e.printStackTrace(); } } } private static final class Customer { private BankAccount bankAccount; public Customer(BankAccount bankAccount) { this.bankAccount = bankAccount; } @SuppressWarnings("unused") public void deposit(double amount) { bankAccount.deposit(amount); } public double withdraw(double amount) { return bankAccount.withdraw(amount); } } private static final class WithdrawTask extends Thread { private Customer customer; private double amount; public WithdrawTask(Customer customer, double amount) { this.amount = amount; this.customer = customer; } @Override public void run() { customer.withdraw(amount); } } }