Por que este método sincronizado não funciona como esperado?

Eu tenho uma class chamada “Conta”

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

e uma class chamada 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); } } 

Eu crio dois tópicos, assumimos que existe um saldo inicial na conta bancária (1500 $)

o primeiro thread tenta retirar 100 $ e o segundo thread também.

Espero que o saldo final seja 1300, no entanto, às vezes é 1400. Alguém pode me explicar por quê? Estou usando methods sincronizados …

Este método está correto e deve ser usado:

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

Ele restringe corretamente o access ao estado interno da conta para apenas um thread por vez. No entanto, seu campo de balance é public (portanto, não muito interno), que é a causa raiz de todos os seus problemas:

 public double balance = 1500; 

Aproveitando public modificador public você está acessando a partir de dois tópicos:

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

Esse método, embora pareça correto com a palavra synchronized chave synchronized , não é. Você está criando dois segmentos e thread synchronized é basicamente um bloqueio vinculado a um object. Isso significa que esses dois segmentos têm bloqueios separados e cada um pode acessar seu próprio bloqueio.

Pense no seu método withDrawFromPrivateBalance() . Se você tiver duas instâncias da class Account , é seguro chamar esse método de dois threads em dois objects diferentes. No entanto, você não pode chamar withDrawFromPrivateBalance() no mesmo object de mais de um thread devido à palavra synchronized chave synchronized . Isso é parecido.

Você pode consertá-lo de duas maneiras: use o withDrawFromPrivateBalance() diretamente (observe que o synchronized não é mais necessário aqui):

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

ou bloquear o mesmo object em ambos os encadeamentos, em oposição ao bloqueio em duas instâncias independentes do object Thread :

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

A última solução é obviamente inferior à anterior porque é fácil esquecer a synchronization externa em algum lugar. Além disso, você nunca deve usar campos públicos.

O seu método private synchronized void find() está sincronizando em diferentes bloqueios. Tente sincronizá-lo nos mesmos objects, como

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

Você também pode tornar sua class Account mais segura para thread, tornando seus campos privados e adicionando um modificador synchronized a todos os getters e setters e, em seguida, usar somente esses methods para alterar o valor dos campos.

Marcar um método sincronizado obtém um bloqueio no object no qual o método está sendo executado, mas há dois objects diferentes aqui: o ATMThread e a Account .

Em qualquer caso, os dois ATMThread diferentes estão usando bloqueios diferentes , portanto suas gravações podem se sobrepor e entrar em conflito entre si.

Em vez disso, você deve ter ambos os ATMThread sincronizados no mesmo object.