Реализация банковского счета на Java

#java #synchronization

#java #синхронизация

Вопрос:

Я новичок в программировании потоков на Java. Чтобы понять потоковую обработку, я пытаюсь написать простую программу для имитации банковского счета. Я только что реализовал вывод средств и пытаюсь его протестировать. Первые несколько строк выходных данных приведены ниже.

Баланс перед выводом T2: 1000
Баланс после вывода T2: 990
Баланс перед выводом T1: 1000
Баланс после вывода T1: 980
Баланс перед выводом T2: 980
Баланс после вывода T2: 970
Баланс перед выводом T1: 970
Баланс после вывода T1: 960

Мой вопрос в том, почему строка 3 (Баланс перед выводом T1: 1000) в выходных данных выдает 1000 вместо 990. Если это было правильно, это должно быть в строке 2. Я что-то упускаю. Верен ли мой подход?

Я предполагаю, что оба потока, пытающиеся выполнить запись для записи на консоль, и поток T1 просто не получили возможности записать это во второй строке.

 class BankAccount {

    private volatile int balance;

    public BankAccount(int b){
        balance = b;
    }

    public BankAccount(){
        balance = 0;
    }


    synchronized public int getBalance(){
        return balance;
    }

    synchronized public int withdraw(int w)
    {
        int b = getBalance();
        if(w <= b){
            balance = balance-w;
            return w;
        }
        else
            return 0;
    }
}

class WithdrawAccount implements Runnable{

    private BankAccount acc;
    private int amount;

    public WithdrawAccount(){
        acc = null;
        amount = 0;
    }

    public WithdrawAccount(BankAccount acc,int amount){
        this.acc = acc;
        this.amount = amount;
    }

    public void run() {
        int w; 

        for(int i =0; i<20; i  ){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            System.out.println("Balance before " Thread.currentThread().getName() " withdrawl: " acc.getBalance());
            w = acc.withdraw(amount);
            System.out.println("Balance after " Thread.currentThread().getName() " withdrawl: " acc.getBalance());
            //System.out.println("amount with drawn by: " Thread.currentThread().getName() " " w);

        }

    }

}

public class TestBankAccount{

    public static void main(String[] args) {

        BankAccount b = new BankAccount(1000);
        WithdrawAccount w = new WithdrawAccount(b,10);
        Thread wt1 = new Thread(w);
        wt1.setName("T1");

        Thread wt2 = new Thread(w);
        wt2.setName("T2");

        wt1.start();
        wt2.start();
    }
}
  

Ответ №1:

Вы ничего не сделали для синхронизации вашего метода run, поэтому значения printlns до и после вывода средств и само снятие средств не являются атомарными. Вы получаете чередование потоков.

Комментарии:

1. Вы синхронизировали учетную запись (там все работает правильно), но если вы хотите, чтобы выходные данные также были в «хорошем» порядке, тогда вам также необходимо синхронизировать их. Но все правильно… когда T1 и T2 выводят средства, счет достигает 1000. После того, как они сняли деньги, это 990 для T1 (я думаю, было быстрее), а для T2 это 980.

Ответ №2:

Вероятно, один поток вызывает withdraw() между этими строками:

 System.out.println("Balance before " Thread.currentThread().getName() " withdrawl: " acc.getBalance());
w = acc.withdraw(amount);
  

Возможный сценарий:

  1. T2 вызывает getBalance();
  2. T1 вызывает getBalance();
  3. T2 выводит баланс;
  4. T2 вызывает withdraw();
  5. T2 выводит баланс после вывода;
  6. T1 выводит баланс;
  7. T1 вызывает withdraw();
  8. T1 выводит баланс после вывода;

Комментарии:

1. Вероятно, нет об этом. Вот что происходит. 1

2. Не совсем. Другой возможный сценарий — когда 7. находится перед 5.

3. Теперь, когда вы отредактировали и добавили возможный сценарий, я бы отредактировал свой предыдущий комментарий. Ваш первоначальный ответ «Вероятно, один поток вызывает вывод между этими строками» — это то, к чему относился мой комментарий.

4. А, ладно. Я не знал, что ваш комментарий появился до того, как я отредактировал id. Моя ошибка.

5. Ошибки нет, Патрик. Все еще хороший ответ, и я не снял свой 1. Кстати, добро пожаловать в StackOverflow.

Ответ №3:

это одно из возможных чередований потоков:

     wt2: calls getBalance()   // retrieves 1000
    wt2: prints "Balance before T2 withdrawl: 1000"

    wt1: calls getBalance()   // retrieves 1000 also

    wt2: acc.withdraw(amount) // balance is now 990
    wt2: prints "Balance after T2 withdrawl: 990"

    wt1: acc.withdraw(amount)    // balance was 990 after wt1 withdraws. wt1 now withdraws again so balance is 980 
    wt1: prints "Balance after T2 withdrawl: 980"
  

Ответ №4:

Это проблема параллелизма

  1. T1 извлекает 1000
  2. T1 выводит «до» в систему из 1000
  3. T2 извлекает 1000
  4. T1 производит вывод
  5. T1 выводит «после» на системный вывод 990
  6. T2 выводит «до» в систему на выходе 1000
  7. T2 производит вывод
  8. T2 выводит «после» на системный вывод 980.

Не удалось точно выполнить в таком порядке, но вы поняли идею. Чтобы заставить его работать так, как вы хотите, используйте synchronized block.

   synchronized (acc) {
    System.out.println("Balance before "   Thread.currentThread().getName()   " withdrawl: "   acc.getBalance());
    w = acc.withdraw(amount);
    System.out.println("Balance after "   Thread.currentThread().getName()   " withdrawl: "   acc.getBalance());
  }