2011-10-02 53 views
0

以下是非线程安全执行流行的丈夫妻子银行账户问题。AtomicInteger不从主存储器读取值为非易失性可变参考

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

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

回答

1

注:这些最初的几个段落描述了这个问题故意缺乏线程安全的,并没有真正回答提问者问的是点..

的检查方法和退出方法,虽然单独是原子操作,但不能组合成单个原子操作。

说丈夫检查帐户,发现有足够的剩余,然后被暂停。

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

丈夫然后被允许继续,并试图收回钱,但发现妻子已经全部消失。

编辑:描述了提问者的问题

你是不是在一个线程安全的方式调用的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); 

这是否帮助显示在竞争条件是什么?

+0

我知道这种行为,但问题是,妻子线程不能从主内存读取ammount值。说,丈夫在旅途中留下了302卢比。在下一行日志中说,妻子检查了546卢比。 – mogli

+0

啊 - 你是否期望System.out是线程安全的并且不会受到竞争条件的影响? –

+0

是不是System.out线程安全? – mogli

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

但是,代码is NOT fixable

 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没有更多。这很好,因为没有其他人可以在支票和实际取款之间得到。