AtomicInteger не читает значение из основной памяти для энергонезависимой изменяемой ссылки - PullRequest
0 голосов
/ 03 октября 2011

Ниже приведена не поточнобезопасная реализация популярной проблемы банковского счета жены мужа.

(Один поток сначала проверяет учетную запись, и, прежде чем тот же поток выполняет вывод, другой поток выполняет операцию снятия, тем самым нарушая код).

Если мы посмотрим журналы программы после выполнения файла Demo.java. Понятно, что «Жена-нить» не считывает значение объема AtomicInteger из основной памяти .

Кроме того, я попробовал тот же пример с простым "volatile int". Но опять же, я столкнулся с той же проблемой: Msgstr "Жена-нить не читает значение целого числа из основной памяти."

Пожалуйста, объясните это поведение, чтобы помочь мне понять концепцию. Пожалуйста, найдите код ниже: -

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

С наилучшими пожеланиями,

РИЦ

Ответы [ 2 ]

1 голос
/ 03 октября 2011

Примечание: эти первые несколько параграфов описывают преднамеренное отсутствие безопасности потока в вопросе, и фактически не отвечают на вопрос, о котором спрашивал спрашивающий ..

Хотя метод проверки и метод извлечения по отдельности являются атомарными, они не объединяются в одну атомарную операцию.

Скажем, муж проверяет учетные записи, находит, что осталось достаточно, а затем отстраняется.

Жена проверяет счета, а затем снимает оставшиеся деньги.

Затем мужу разрешают продолжить, и он пытается снять деньги, но обнаруживает, что жена уже ушла со всем этим.

Редактировать: описывает причину вопроса спрашивающего

Вы не вызываете 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);

Показывает ли это, где находится состояние гонки?

1 голос
/ 03 октября 2011

Этот код выглядит подозрительно:

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 больше нет. Это хорошо, потому что таким образом никто не сможет попасть между чеком и фактическим снятием денег.

...