为什么这个同步方法不能按预期工作?

我有一个名为“帐户”的课程

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

还有一个名为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); } } 

我创建两个线程,我们假设银行账户中有一个初始余额(1500 $)

第一个线程尝试撤销100 $和第二个线程。

我预计最终余额为1300,但有时候是1400.有人可以解释一下为什么吗? 我正在使用同步方法……

这种方法是正确的,应该使用:

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

它正确地将对帐户内部状态的访问限制为一次只有一个线程。 但是你的balance字段是public (所以不是真正的内部),这是所有问题的根本原因:

 public double balance = 1500; 

利用public修饰符,您可以从两个线程访问它:

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

这种方法,即使使用synchronized关键字看起来正确,也不是。 您正在创建两个线程,并且synchronized线程基本上是一个绑定到对象的锁。 这意味着这两个线程具有单独的锁,每个都可以访问自己的锁。

想想你的withDrawFromPrivateBalance()方法。 如果您有两个Account类实例,则可以安全地从两个不同对象上的两个线程调用该方法。 但是,由于synchronized关键字,您无法在多个线程的同一对象上调用withDrawFromPrivateBalance() 。 这有点类似。

您可以通过两种方式修复它:直接使用withDrawFromPrivateBalance() (请注意,此处不再需要synchronized ):

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

或者锁定两个线程中的同一个对象,而不是锁定两个独立的Thread对象实例:

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

后一种解决方案明显不如前者,因为很容易忘记某处的外部同步。 你也不应该使用公共领域。

您的private synchronized void find()方法正在同步不同的锁。 尝试在相同的对象上同步它

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

您还可以通过将其字段设为私有并将synchronized修饰符添加到其所有 getter和setter,然后仅使用此方法更改字段值来使您的Account类更安全。

标记同步方法会获得对运行该方法的对象的锁定,但此处有两个不同的对象: ATMThreadAccount

无论如何,两个不同的ATMThread正在使用不同的锁,因此它们的写入可能会重叠并相互冲突。

相反,您应该让两个ATMThread同步在同一个对象上。