AtomicInteger不从主存储器读取值以用于非易失性可变参考

时间:2011-10-02 20:56:59

标签: java multithreading thread-safety volatile atomic-values

以下是一个非线程安全的实施流行的丈夫妻子银行帐户问题。

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

如果我们在执行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) ;
            }
        }
    }
}

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

2 个答案:

答案 0 :(得分: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的访问之间,另一个线程可以进入并破坏havok。您必须使代码适应线程安全。

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

    // 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 :(得分: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);

这有助于显示竞争条件的位置吗?