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

时间:2012-06-17 18:38:16

标签: java multithreading synchronized

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

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.有人可以解释一下原因吗?我正在使用同步方法...

3 个答案:

答案 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)

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

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

相反,您应该在相同的对象上同时进行ATMThread个同步。