0

以下は、人気のある夫の妻の銀行口座の問題の非スレッドセーフな実装です。

(1つのスレッドが最初にアカウントをチェックし、同じスレッドがwithdrawを実行する前に、別のスレッドがwithdraw操作を実行するため、コードが壊れます)。

Demo.javaファイルの実行後にプログラムのログを見ると。「WifeThread」がメインメモリからAtomicInteger量の値を読み取っていないことは明らかです。

また、単純な「volatileint」を使用して同じ例を試しました。しかし、繰り返しになりますが、私は同じ問題に直面しています:-「Wife-Threadはメインメモリから整数の値を読み取っていません。」

概念を理解するのに役立つように、この動作を説明してください。以下のコードを見つけてください:-

AtomicBankAccount.java

package pack;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicBankAccount {

    private AtomicInteger amount ;

    public AtomicBankAccount(int amt) {
        this.amount = new AtomicInteger(amt) ;
    }

    // returns
    // -1 for insufficient funds
    // remaining balance without subtracting from actual amount for sufficient funds
    public int check(int amtToWithdraw){

        if(amtToWithdraw <= amount.get()){
            System.out.println(Thread.currentThread().getName() + " checks amount : " + amount.get() + ". Remaining ammount after withdrawl should be : " + (amount.get() - amtToWithdraw));
            return (amount.get() - amtToWithdraw) ;
        }else{
            return -1 ;
        }
    }

    // returns
    // remaining balance after subtracting from actual amount
    public int withdraw(int amtToWithdraw){
        amount.getAndAdd(-amtToWithdraw) ;
        System.out.println(Thread.currentThread().getName() + " withdraws " + amtToWithdraw + ". Remaining : " + amount.get() + " [latest updated value of account in main memory]");
        return amount.get() ;
    }

    public int getAmount(){
        return amount.get() ;
    }
}

AtomicWithdrawThread.java

package pack;

public class AtomicWithdrawThread extends Thread{ 

    private AtomicBankAccount account ;

    public AtomicWithdrawThread(AtomicBankAccount acnt, String name) {
        super(name) ;
        this.account = acnt ;
    }

    @Override
    public void run() {
        int withDrawAmt = 2 ;
        int remaining = 0 ;
        while(true){

            if( (remaining = account.check(withDrawAmt)) != -1 ){
                int temp = account.withdraw(withDrawAmt) ;
                if(temp != remaining){
                    System.out.println("[Race condition] " + Thread.currentThread().getName());
                    System.exit(1) ;
                }
            }else{
                System.out.println("Empty Account....");
                System.exit(1) ;
            }
        }
    }
}

Demo.java

package pack;

public class Demo {

    public static void main(String[] args) {
        AtomicBankAccount bankAccount = new AtomicBankAccount(1000) ;

        AtomicWithdrawThread husbandThread = new AtomicWithdrawThread(bankAccount, "husband") ;
        AtomicWithdrawThread wifeThread = new AtomicWithdrawThread(bankAccount, "wife") ;

        husbandThread.start() ;
        wifeThread.start() ;
    }
}

よろしくお願いします、

rits

4

2 に答える 2

1

そのコードは怪しいように見えます:

amount.getAndAdd(-amtToWithdraw) ;
return amount.get() ;

その間に別のスレッドが忍び寄ると... 面白いことが起こる可能性があります。代わりにそのコードを使用してテストします(これもSystem.outお願いします):

int amt = amount.getAndAdd(.amtToWithdraw);
return amt - amtToWithdraw;

そしてここでも:

   if(amtToWithdraw <= amount.get()){
       return (amount.get() - amtToWithdraw) ;

パターンを再度使用する

    int amt = amount.get();
    if(amtToWithdraw <= amt){
        return (amt - amtToWithdraw) ;

しかし、そのコードは修正できません:

        if( (remaining = account.check(withDrawAmt)) != -1 ){
            int temp = account.withdraw(withDrawAmt) ;

それらの他のスレッドへのアクセスの間に、AtomicInteger忍び寄って大混乱を招く可能性があります。スレッドセーフになるようにコードを調整する必要があります。

通常のパターン/イディオムは次のようになります。

    // In AtomicBankAccount
    public int withdraw(int amtToWithdraw){
        for(;;){
            int oldAmt = amount.get();
            int newAmt = oldAmt - amtToWithdraw;
            if( newAmt < 0 )
                return -1;
            if( amount.compareAndSet(oldAmt, newAmt) ){
                System.out.println(Thread.currentThread().getName() + " withdraws " + amtToWithdraw + ". Remaining : " + newAmt + " [latest updated value of account in main memory]");      
                return newAmt;
            }
        }
    }

    // in AtomicWithdrawThread:
    public void run() {
        int withDrawAmt = 2 ;
        while(true){
            if( account.withdraw(withDrawAmt) >= 0 ){
                // OK
            }
            else{
                System.out.println("Empty Account....");
                System.exit(1) ;
            }
        }
    }

もうないことに注意してくださいcheckWithdraw。そうすれば、他の誰も小切手と実際の引き出しの間に入ることができないので、それは良いことです.

于 2011-10-02T21:14:42.550 に答える
1

注: これらの最初の数段落は、質問でスレッド セーフが意図的に欠如していることを説明しており、質問者が尋ねていた点に実際には答えていません。

check メソッドと draw メソッドは、個別にはアトミックですが、結合して単一のアトミック操作にはなりません。

夫がアカウントをチェックし、十分な残高があることを確認し、停止されたとします。

妻は口座をチェックし、残りのお金を引き出します。

その後、夫は続行を許可され、お金を引き出そうとしますが、妻はすでにすべてを使い果たしていることに気付きます。

編集:質問者の問題の理由を説明します

スレッドセーフな方法で System.out を呼び出していません。表示しようとしているメッセージの計算と、実際にコンソールに表示することの間には競合状態があります。そのため、妻のメッセージはおそらく夫の撤回前に計算されますが、その後に表示されます。

この影響を排除したい場合は、これらの System.out 行 (または同等のもの) の周りに同期キーワードを追加する必要があります。

コードが実際に次のようになっていると想像してみてください。

String message = Thread.currentThread().getName() + " checks amount : " + amount.get() + ". Remaining ammount after withdrawl should be : " + (amount.get() - amtToWithdraw);
System.out.println(message);

これは、競合状態がどこにあるかを示すのに役立ちますか?

于 2011-10-02T21:15:25.583 に答える