【问题标题】:why doesn't this synchronized method work as expected?为什么这种同步方法不能按预期工作?
【发布时间】:2012-06-19 21:23:17
【问题描述】:

我有一个名为“Account”的类

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

【问题讨论】:

    标签: java multithreading synchronized


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

          猜你喜欢
          • 2014-12-24
          • 2016-01-16
          • 1970-01-01
          • 2020-09-14
          • 2016-11-11
          • 1970-01-01
          • 1970-01-01
          • 2017-06-08
          • 1970-01-01
          相关资源
          最近更新 更多