首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >不从主存读取非易失性可变参考值的AtomicInteger

不从主存读取非易失性可变参考值的AtomicInteger
EN

Stack Overflow用户
提问于 2011-10-02 20:56:58
回答 2查看 281关注 0票数 0

下面是一个流行的丈夫妻子银行账户的non-thread-safe实现问题。

(一个线程首先检查帐户,在同一个线程执行取款之前,另一个线程执行退出操作,从而破坏代码)。

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

此外,我也用普通的“易失性int”尝试了相同的例子。但是,我也面临着同样的问题:“妻子-线程不是从主内存读取整数的值。”

请解释一下这个行为,以帮助我理解这个概念。请查找以下代码:-

AtomicBankAccount.java

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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() ;
    }
}

诚挚的问候,

里兹

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2011-10-02 21:15:25

注意:前几段描述了问题中故意缺乏线程安全性的问题,并且实际上没有回答发问者询问的关于..的问题。

检查方法和提取方法虽然各自是原子的,但不要合并成单个原子操作。

比如丈夫检查账户,发现有足够的剩余,然后被停职。

妻子检查帐户,然后取回剩余的钱。

然后,丈夫被允许继续,并试图取款,但发现妻子已经走了这一切。

编辑:描述发问者发布的原因

您不能以线程安全的方式调用System.out。在计算你将要显示的信息和实际让它出现在控制台之间有一个竞争条件--所以妻子的信息可能是在丈夫退出之前计算出来的,而是在丈夫退出之后显示出来的。

如果要消除这种影响,需要在这些System.out行(或类似的内容)周围添加同步关键字。

想象一下您的代码实际上如下所示:

代码语言:javascript
复制
String message = Thread.currentThread().getName() + " checks amount : " + amount.get() + ". Remaining ammount after withdrawl should be : " + (amount.get() - amtToWithdraw);
System.out.println(message);

这能说明比赛条件在哪里吗?

票数 1
EN

Stack Overflow用户

发布于 2011-10-02 21:14:42

这段代码看起来很可疑:

amount.getAndAdd(-amtToWithdraw);返回amount.get();

如果另一根线在那之间爬行.有趣的事情可能会发生。使用并测试该代码(也请在System.out中):

代码语言:javascript
复制
int amt = amount.getAndAdd(.amtToWithdraw);
return amt - amtToWithdraw;

此外,在此:

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

再次使用该模式

代码语言:javascript
复制
    int amt = amount.get();
    if(amtToWithdraw <= amt){
        return (amt - amtToWithdraw) ;

但这段代码是,不是可修复的

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

在这些对AtomicInteger的访问之间,另一个线程可以潜入并损坏。必须对代码进行调整,使之成为线程安全的代码。

通常的模式/成语是这样的:

代码语言:javascript
复制
    // 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不再是。这很好,因为那样的话,其他人就不能在支票和实际提款之间得到。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7629172

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档