2012-06-17 13 views
5

He una clase llamada "Cuenta"¿por qué este método sincronizado no funciona como se espera?

public class Account { 

    public double balance = 1500; 

    public synchronized double withDrawFromPrivateBalance(double a) { 
     balance -= a; 
     return balance; 
    } 
} 

y una clase llamada ATMThread

public class ATMThread extends Thread { 
    double localBalance = 0; 
    Account myTargetAccount; 

    public ATMThread(Account a) { 
     this.myTargetAccount = a; 
    } 

    public void run() { 
     find(); 
    } 

    private synchronized void find() { 
     localBalance = myTargetAccount.balance; 
     System.out.println(getName() + ": local balance = " + localBalance); 
     localBalance -= 100; 
     myTargetAccount.balance = localBalance; 
    } 

    public static void main(String[] args) { 
     Account account = new Account(); 
     System.out.println("START: Account balance = " + account.balance); 

     ATMThread a = new ATMThread(account); 
     ATMThread b = new ATMThread(account); 

     a.start(); 
     b.start(); 

     try { 
      a.join(); 
      b.join(); 
     } catch (InterruptedException ex) { 
      ex.printStackTrace(); 
     } 

     System.out.println("END: Account balance = " + account.balance); 
    } 

} 

puedo crear dos hilos, suponemos que hay un saldo inicial de la cuenta bancaria (1500 $)

el primer subproceso intenta retirar 100 $ y el segundo subproceso también.

Espero que el saldo final sea 1300, sin embargo, a veces es 1400. ¿Alguien me puede explicar por qué? Estoy usando métodos sincronizados ...

Respuesta

12

Este método es correcto y se debe utilizar:

public synchronized double withDrawFromPrivateBalance(double a) 
{ 
      balance -= a; 
      return balance; 
} 

restringe correctamente el acceso a la cuenta de estado interno a un solo hilo a la vez. Sin embargo su campo balance es public (así que no es interno), que es la causa de todos sus problemas:

public double balance = 1500; 

Aprovechando public modificador que está accediendo desde dos hilos:

private synchronized void find(){ 
    localBalance = myTargetAccount.balance; 
    System.out.println(getName() + ": local balance = " + localBalance); 
    localBalance -= 100; 
    myTargetAccount.balance = localBalance; 
} 

Este método, aunque parece correcto con la palabra clave synchronized, no lo es. Está creando dos hilos y el hilo synchronized es básicamente un bloqueo vinculado a un objeto. Esto significa que estos dos hilos tienen bloqueos separados y cada uno puede acceder a su propio bloqueo.

Piensa en tu método withDrawFromPrivateBalance(). Si tiene dos instancias de clase Account, es seguro llamar a ese método desde dos subprocesos en dos objetos diferentes. Sin embargo, no puede llamar al withDrawFromPrivateBalance() en el mismo objeto desde más de un hilo debido a la palabra clave synchronized. Esto es algo así como similar.

Puede solucionar de dos maneras: o bien utilizar withDrawFromPrivateBalance() directamente (tenga en cuenta que synchronized ya no se necesita aquí):

private void find(){ 
    myTargetAccount.withDrawFromPrivateBalance(100); 
} 

o bloquear en el mismo objeto en los dos hilos en lugar de bloqueo en dos independientes Thread instancias de objetos:

private void find(){ 
    synchronized(myTargetAccount) { 
     localBalance = myTargetAccount.balance; 
     System.out.println(getName() + ": local balance = " + localBalance); 
     localBalance -= 100; 
     myTargetAccount.balance = localBalance; 
    } 
} 

Esta última solución es evidentemente inferior a la anterior, ya que es fácil olvidarse de sincronización externa alguna parte.Además, nunca deberías usar campos públicos.

4

Al marcar un método sincronizado se obtiene un bloqueo en el objeto sobre el que se está ejecutando el método, pero aquí hay dos objetos diferentes: el ATMThread y el Account.

En cualquier caso, los dos ATMThread s diferentes están usando cerraduras diferentes, por lo que sus escrituras pueden superponerse y entrar en conflicto entre sí.

En su lugar, debe tener ambos ATMThread s sincronizar en el objeto mismo objeto.

8

Su método private synchronized void find() se sincroniza en diferentes bloqueos. Intente la sincronización en los mismos objetos como

private void find(){ 
    synchronized(myTargetAccount){ 
     localBalance = myTargetAccount.balance; 
     System.out.println(getName() + ": local balance = " + localBalance); 
     localBalance -= 100; 
     myTargetAccount.balance = localBalance; 
    } 
} 

También puede hacer que su clase Account más hilo de seguridad, haciendo que sus campos privados y añadiendo synchronized modificador a todos sus captadores y definidores y luego Sólo debe utilizarse métodos para cambiar el valor de los campos

Cuestiones relacionadas