У меня есть пример, похожий на тот, что в этом вопросе.
После того, как я гарантирую сохранение порядка полученных замков
public class Account {
private int balance;
public void withdraw(int value) {
balance -= value;
}
public void deposit(int value) {
balance += value;
}
}
public class Bank {
private List<Account> accounts;
private int slots;
public Bank(int slots) {
accounts = new ArrayList<Account>(Collections.nCopies(slots, new Account()));
this.slots = slots;
}
public void transfer(int fromId, int toId, int value) {
synchronized(accounts.get(Math.min(fromId, toId))) {
synchronized(accounts.get(Math.max(fromId, toId))) {
accounts.get(fromId).withdraw(value);
accounts.get(toId).deposit(value);
}
}
}
Я понимаю, что если один поток выполняет передачу из учетной записи A в B, а другой поток хочет сделать передачу из B в A, тогда он должен ждать блокировки (исправьте меня, если я ошибаюсь).
Каков наилучший способ гарантировать, что только один поток может сделать передачу от A до B, но другой поток может переходить от B к A?
Я думал об объявлении двух наборов блокировок для каждого объекта Account:
public class Account{
public Object fromLock = new Object();
public Object toLock = new Object();
// ...
}
И получить один из них в зависимости от того, переводится ли она с данной учетной записи или с нее. Это правильный путь?
РЕДАКТИРОВАТЬ: Вопрос: предположим, что методы снятия и сдачи требуют времени. Как бы вы разрешили двум пользователям (связанным с разными потоками) переводить друг друга в одно и то же время, не дожидаясь ожидания?
Как вы внедрили Учетную запись, вы никогда не захотите, чтобы несколько транзакций влияли на одну учетную запись в любой момент времени. В частности, поскольку "баланс" - это int, а "вывести" и "депозит" напрямую изменить значение "баланса" напрямую, используя неатомные операции, вы никогда не захотите, чтобы вызывы "вывести" и "депозит" чередовали,
С другой стороны, если бы вы представляли "баланс" как AtomicInteger, тогда призывы "снять" и "депозит" можно было бы смело перемешать. Однако, если вы использовали как "fromLock", так и "toLock", то максимум одна учетная запись могла одновременно переводить деньги на любую учетную запись. Например, предположим, что учетные записи B и C оба хотят перевести деньги на счет A - используя "toLock", эти две передачи должны быть сериализованы.
Однако, отступив немного, почему необходимо предотвратить одновременные депозиты в данную учетную запись (при условии, что баланс счета мутируется поточно-безопасным образом)? Конечно, необходимо обеспечить, чтобы любая конкретная учетная запись могла быть задействована не более одной транзакции в качестве учетной записи "из" в любой момент времени; но я не понимаю, почему такое ограничение должно существовать для учетной записи "to" (опять же, считая, что "баланс" управляется поточно-безопасным образом).
Итак, если я не пропущу что-то здесь, я думаю, что представлять "баланс" как AtomicInteger и только захват блокировки учетной записи "from" внутри "transfer" должен удовлетворять вашим требованиям.
ПРИМЕЧАНИЕ. Прежде чем я вернусь в забвение, позвольте мне сказать, что мои комментарии применимы только к конкретному способу представления банковских счетов в вашем образце кода (т.е. В производственных системах, которые делают такие вещи, есть веские причины для обеспечения того, чтобы парные депозиты и снятие средств осуществляются в рамках транзакций).
mBalance = 0
это не нужно в Java. Вы можете прочитать docs.oracle.com/javase/tutorial/java/nutsandbolts/… для получения дополнительной информации. Также не часто имена ваших переменных называются «m» en.wikipedia.org/wiki/Naming_convention_(programming)#Java