0

Below is a non-thread-safe implementation of popular Husband Wife bank account problem.

(One thread first checks the account, and before the same thread performs withdraw, another thread performs withdraw operation, thus breaking the code).

If we look at the logs of the program after execution of Demo.java file. It is clear that, "Wife Thread" is not reading value of AtomicInteger amount from main memory.

Also, I tried the same example with plain "volatile int". But again, I am facing the same problem :- "Wife-Thread is not reading value of amount integer from main memory."

Kindly explain this behaviour to help me understand the concept. Please find the code below :-

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

Best Regards,

rits

mogli
  • 1,549
  • 4
  • 29
  • 57

2 Answers2

1

That code looks fishy:

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

If the other thread creeps between that... funny things can happen. Use and test that code instead (also in the System.out please):

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

And here also:

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

use again the pattern

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

But that code is NOT fixable:

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

Between those accesses to the AtomicInteger the other thread can creep in and wreck havok. You must adapt the code to be thread safe.

A usual pattern/idiom is like this:

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

Note that checkWithdraw is no more. That`s good because that way no one else can get get between the check and the actual withdrawal.

A.H.
  • 63,967
  • 15
  • 92
  • 126
1

Note: These first few paragraphs describe the deliberate lack of thread safety in the question, and don't actually answer the point the questioner was asking about..

The check method and the withdraw method, while individually are atomic, don't combine into a single atomic operation.

Say Husband checks the accounts, finds there is enough remaining, and then gets suspended.

Wife checks the accounts, then withdraws the remaining money.

Husband is then allowed to continue, and tries to withdraw the money, but finds wife has already gone off with it all.

Edit: Describes the reason for the questioner's issue

You aren't calling System.out in a thread-safe way. There is a race condition between calculating the message that you are going to display and actually getting it to appear on the console - so the wife's message is probably calculated before the husband's withdrawal, but displayed after it.

You need to add synchronized keywords around those System.out lines (or something equivalent) if you want to eliminate this effect.

Just imagine your code actually looks like this:

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

Does this help show where the race condition is?

Bill Michell
  • 8,240
  • 3
  • 28
  • 33
  • I know this behaviour, but the problem is that, wife thread not reading ammount value from main memory. Say, husband left 302 Rs in ammount. In the next line logs says, that, wife checks 546 Rs in account. – mogli Oct 02 '11 at 21:24
  • Ah - are you expecting System.out to be thread safe and not suffer from race conditions? – Bill Michell Oct 02 '11 at 21:44
  • Actually, PrintStream (which System.out is), has lots of synchronized code in it to make it thread safe. The question here is rather *when* a thread gets a chance to do IO. – forty-two Oct 02 '11 at 22:05
  • OK so I've reworded my answer to make it clearer that the race condition is inside the client's code, not in System.out itself. – Bill Michell Oct 02 '11 at 22:11
  • And now I've added a snippet of code with a refactoring of the original to make it clearer which race condition I am trying to describe. – Bill Michell Oct 02 '11 at 22:17
  • 1
    Thanks Bill. After wrapping print statements in synchronizing block. Code is working as expected. – mogli Oct 03 '11 at 06:57
  • If my answer is the "correct" one, would you mind accepting it? – Bill Michell Oct 03 '11 at 13:50