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

}

2 つのスレッドを作成します。銀行口座に初期残高 (1500$) があると仮定します。

最初のスレッドは 100 ドルを引き出そうとし、2 番目のスレッドも同様です。

最終的な残高は 1300 になると予想していますが、1400 になることもあります。誰か理由を説明してもらえますか? 私は同期された方法を使用しています...

4

3 に答える 3

17

この方法は正しく、使用する必要があります。

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

アカウントの内部状態へのアクセスを、一度に 1 つのスレッドのみに正しく制限します。ただし、balanceフィールドはpublic(実際には内部ではない)であり、これがすべての問題の根本原因です。

public double balance = 1500;

修飾子を利用publicして、2 つのスレッドからアクセスしています。

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

synchronizedこのメソッドは、キーワードで正しいように見えますが、そうではありません。2 つのスレッドを作成していますが、synchronizedスレッドは基本的にオブジェクトに関連付けられたロックです。これは、これら 2 つのスレッドが個別のロックを持ち、それぞれが独自のロックにアクセスできることを意味します。

あなたのwithDrawFromPrivateBalance()方法を考えてください。クラスのインスタンスが 2 つある場合、Account2 つの異なるオブジェクトの 2 つのスレッドからそのメソッドを安全に呼び出すことができます。ただし、キーワードwithDrawFromPrivateBalance()により、複数のスレッドから同じオブジェクトを呼び出すことはできません。synchronizedこれは似たようなものです。

2 つの方法で修正できます: 直接使用します (ここではもう必要ないことにwithDrawFromPrivateBalance()注意してください)。synchronized

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

Threadまたは、2 つの独立したオブジェクト インスタンスをロックするのではなく、両方のスレッドで同じオブジェクトをロックします。

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

フィールドをプライベートにし、すべてのゲッターとセッターにsynchronized修飾子を追加してから、このメソッドのみを使用してフィールドの値を変更することにより、 Account クラスをよりスレッドセーフにすることもできます。

于 2012-06-17T18:50:35.400 に答える
4

メソッドを同期とマークすると、メソッドが実行されているオブジェクトのロックが取得されますが、ここにはATMThreadと の2 つの異なるオブジェクトがありAccountます。

いずれにせよ、2 つの異なるは異なるATMThreadロックを使用しているため、それらの書き込みが重複して互いに競合する可能性があります。

代わりに、同じオブジェクトで両方ATMThreadの を同期させる必要があります。

于 2012-06-17T18:47:32.890 に答える