0

下面是流行的丈夫妻子银行账户问题的非线程安全实现。

(一个线程先检查账户,在同一个线程进行提现之前,另一个线程进行提现操作,从而破坏了代码)。

如果我们在执行 Demo.java 文件后查看程序的日志。很明显,“妻子线程”不是从主内存中读取 AtomicInteger 数量的值

另外,我用普通的“volatile int”尝试了同样的例子。但同样,我面临同样的问题:-“妻子线程没有从主存储器中读取数量整数的值。”

请解释这种行为以帮助我理解这个概念。请在下面找到代码:-

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

演示.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() ;
    }
}

最好的祝福,

仪式

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 方法和withdraw 方法虽然单独是原子的,但不会组合成一个原子操作。

假设丈夫检查帐户,发现余额足够,然后被暂停。

妻子检查账户,然后取出剩余的钱。

然后丈夫被允许继续,并试图提取钱,但发现妻子已经全部离开。

编辑:描述提问者问题的原因

您没有以线程安全的方式调用 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 回答