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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11073841/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 03:40:50  来源:igfitidea点击:

why doesn't this synchronized method work as expected?

javamultithreadingsynchronized

提问by ksm001

I have a class called "Account"

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

public class Account {

    public double balance = 1500;

    public synchronized double withDrawFromPrivateBalance(double a) {
        balance -= a;
        return balance;
    }
}

and a class called ATMThread

和一个叫做 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);
    }

}

I create two threads, we assume there is an initial balance in the bank account(1500$)

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

the first thread tries to withdraw 100$ and the second thread as well.

第一个线程尝试提取 100 美元,第二个线程也尝试提取。

I expect the final balance to be 1300, however it is sometimes 1400. Can someone explain me why? I'm using synchronized methods...

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

回答by Tomasz Nurkiewicz

This method is correct and should be used:

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

public synchronized double withDrawFromPrivateBalance(double a)
{
          balance -= a;
          return balance;
}

It correctly restricts access to the account internal state to only one thread at a time. However your balancefield is public(so not really internal), which is the root cause of all your problems:

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

public double balance = 1500;

Taking advantage of publicmodifier you are accessing it from two threads:

利用public修饰符从两个线程访问它:

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

This method, even though looks correct with synchronizedkeyword, it is not. You are creating two threads and synchronizedthread is basically a lock tied to an object. This means these two threads have separate locks and each can access its own lock.

这个方法,虽然用synchronized关键字看起来是正确的,其实不然。您正在创建两个线程,synchronized线程基本上是一个绑定到对象的锁。这意味着这两个线程有​​单独的锁,每个线程都可以访问自己的锁。

Think about your withDrawFromPrivateBalance()method. If you have two instances of Accountclass it is safe to call that method from two threads on two different objects. However you cannot call withDrawFromPrivateBalance()on the same object from more than one thread due to synchronizedkeyword. This is sort-of similar.

想想你的withDrawFromPrivateBalance()方法。如果您有两个Account类的实例,那么从两个不同对象上的两个线程调用该方法是安全的。但是,withDrawFromPrivateBalance()由于synchronized关键字的原因,您不能从多个线程调用同一个对象。这有点相似。

You can fix it in two ways: either use withDrawFromPrivateBalance()directly (note that synchronizedis no longer needed here):

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

private void find(){
    myTargetAccount.withDrawFromPrivateBalance(100);
}

or lock on the same object in both threads as opposed to locking on two independent Threadobject instances:

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

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

The latter solution is obviously inferior to the former one because it is easy to forget about external synchronization somewhere. Also you should never use public fields.

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

回答by Pshemo

Your private synchronized void find()method is synchronizing on different locks. Try synchronizing it on same objects like

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

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

You can also make your Account class more thread safe, by making its fields private and adding synchronizedmodifier to allits getters and setters and then use only this methodsto change value of fields.

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

回答by Louis Wasserman

Marking a method synchronized obtains a lock on the object that the method is being run on, but there are two different objects here: the ATMThreadand the Account.

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

In any event, the two different ATMThreads are using differentlocks, so their writes can overlap and conflict with one another.

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

Instead, you should have both ATMThreads synchronize on the sameobject.

相反,您应该ATMThread同一个对象上同步。