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

ksm*_*001 6 java multithreading synchronized

我有一个名为"帐户"的班级

public class Account {

    public double balance = 1500;

    public synchronized double withDrawFromPrivateBalance(double a) {
        balance -= a;
        return balance;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

我创建了两个主题,我们假设银行账户中有一个初始余额(1500美元)

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

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

Tom*_*icz 15

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

public synchronized double withDrawFromPrivateBalance(double a)
{
          balance -= a;
          return balance;
}
Run Code Online (Sandbox Code Playgroud)

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

public double balance = 1500;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

private void find(){
    myTargetAccount.withDrawFromPrivateBalance(100);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


Psh*_*emo 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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