#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);
Возможный сценарий:
- T2 вызывает getBalance();
- T1 вызывает getBalance();
- T2 выводит баланс;
- T2 вызывает withdraw();
- T2 выводит баланс после вывода;
- T1 выводит баланс;
- T1 вызывает withdraw();
- 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:
Это проблема параллелизма
- T1 извлекает 1000
- T1 выводит «до» в систему из 1000
- T2 извлекает 1000
- T1 производит вывод
- T1 выводит «после» на системный вывод 990
- T2 выводит «до» в систему на выходе 1000
- T2 производит вывод
- 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());
}