我有一个名为“帐户”的课程
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.有人可以解释一下原因吗?我正在使用同步方法...
答案 0 :(得分:15)
此方法是正确的,应该使用:
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
类实例,则可以安全地从两个不同对象上的两个线程调用该方法。但是,由于withDrawFromPrivateBalance()
关键字,您无法从多个线程调用同一对象上的synchronized
。这有点类似。
您可以通过两种方式解决此问题:直接使用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;
}
}
后一种解决方案明显不如前一种解决方案,因为很容易忘记某处的外部同步。你也不应该使用公共领域。
答案 1 :(得分:8)
您的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,然后仅使用此方法<来使您的帐户类更安全线程安全/ strong>改变字段的值。
答案 2 :(得分:4)
将方法标记为同步会获得对正在运行该方法的对象的锁定,但此处有两个不同的对象:ATMThread
和Account
。
无论如何,两个不同的ATMThread
正在使用不同的锁,因此它们的写入可能会重叠并相互冲突。
相反,您应该在相同的对象上同时进行ATMThread
个同步。