8

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

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。有人能解释一下为什么吗?我正在使用同步方法...

4

3 回答 3

17

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

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;
    }
}

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

于 2012-06-17T18:48:22.630 回答
9

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

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

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

于 2012-06-17T18:50:35.400 回答
4

将方法标记为同步会获得对该方法正在运行的对象的锁定,但这里有两个不同的对象:theATMThreadAccount.

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

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

于 2012-06-17T18:47:32.890 回答