Синхронизация Java не работает как (I).

79
9

программисты. Я тестировал java-потоковые возможности с помощью очень простого кода (или, по крайней мере, он казался простым). У меня есть учетная запись этого класса:


public class Account {
protected double balance;

public synchronized void withdraw(double value) {
this.balance = this.balance - value;
}

public synchronized void deposit(double value) {
this.balance = this.balance + value;
}

public synchronized double getBalance() {
return this.balance;
}
}


И у меня есть два потока: Depositer, который откладывает $10 тысяч раз:


public class Depositer extends Thread {
protected Account account;

public Depositer(Account a) {
account = a;
}

@Override
public void run() {
for(int i = 0; i < 1000; i++) {
this.account.deposit(10);
}
}
}


И Withdrawer, который снимает $10 тысяч раз:


public class Withdrawer extends Thread {
protected Account account;

public Withdrawer(Account a) {
account = a;
}

@Override
public void run() {
for(int i = 0; i < 1000; i++) {
this.account.withdraw(10);
}
}
}


Эта компоновка выполняется:


public class Main {
public static void main(String[] args) {
Account account = new Account();
Thread c1 = new Depositer(account);
Thread c2 = new Withdrawer(account);

c2.start();
c1.start();

System.out.println(account.getBalance());
}
}


Поскольку методы синхронизированы, я просто ожидал, что баланс всегда будет 0 в конце, но иногда это не происходит. И я искренне не могу понять, почему. Кто-нибудь может увидеть, где моя ошибка?

спросил(а) 2021-01-19T16:34:41+03:00 9 месяцев назад
1
Решение
122

c2.start() и c1.start() запускает процесс асинхронно. Ваш основной поток должен дождаться завершения этих двух потоков, прежде чем распечатывать результаты.


Вызов


try {
c2.join();
c1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

перед вызовом println.


См. join.


Ожидает, что этот поток погибнет.


ответил(а) 2021-01-19T16:34:41+03:00 9 месяцев назад
112

Вы делаете следующее:


c2.start();  // Start a thread in the background
c1.start(); // Start a 2nd thread in the background

// print out the balance while both threads are still running
System.out.println(account.getBalance());


Вам нужно дождаться завершения этих потоков:

c2.start();  // Start a thread in the background
c1.start(); // Start a 2nd thread in the background

try {
c2.join(); // Wait until the c2 thread completes
c1.join(); // Wait until the c1 thread completes
} catch (InterruptedException e) {
// LOG AN ERROR HERE
}

// print out the final balance
System.out.println(account.getBalance());


Если вы прерываете свой основной поток, вам нужно что-то сделать с прерванным исключением. Предполагая, что ни один из ваших кодов не делает этого, вы должны всегда, как минимум, регистрировать Исключение. ПРИМЕЧАНИЕ. Вы не получите InterruptedException, если кто-то прерывает c1 или c2, но если кто-то прерывает ваш основной поток, поток, который вызывает join(). Если кто-то назвал interrupt() в вашем основном потоке, но вы не проверяете его, тогда вы, вероятно, получите InterruptedException в тот момент, когда вы вызываете join().

ответил(а) 2021-01-19T16:34:41+03:00 9 месяцев назад
79

В многопоточном коде определение "конец" сложно.


Синхронизация означает, что каждая операция в учетной записи является взаимоисключающей. То есть учетная запись не обновляется в момент внесения депозита, снятия или проверки баланса.


Однако ваша распечатка баланса происходит из основной программы сразу после появления нитей. Поскольку они независимые потоки, нет гарантий, что они заканчиваются, прежде чем вы попадете на печать. Следовательно, вы можете печатать до того, как вы сделаете подсчет до 1000. Или, если это по-другому, сумма равна 0 в конце, но вы не печатаете в конце...


Вы можете присоединиться к потокам с основного.

ответил(а) 2021-01-19T16:34:41+03:00 9 месяцев назад
79

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


c1.join();
c2.join();
System.out.println(account.getBalance());

ответил(а) 2021-01-19T16:34:41+03:00 9 месяцев назад
65

Этот тип ошибки concurrency называется Состояние гонки. Вам нужно синхронизировать потоки. Используя методы join, как объясняют другие, вы эффективно реализуете параллельный Barrier.

ответил(а) 2021-01-19T16:34:41+03:00 9 месяцев назад
46

Вы заметите, что вывод такой же, как добавление отрицательной суммы, поэтому вам это действительно не нужно.


сумма должна быть частной, не защищенной, так как все операции, которые вы можете выполнить на ней, должны быть определены в Учетной записи и синхронизированы.

ответил(а) 2021-01-19T16:34:41+03:00 9 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема