Как сделать потокобезопасным основанный на ConcurrentHashMap метод?
В целях обучения параллельности / многопоточности я разрабатываю API-интерфейс для небольших денежных переводов, который будет одновременно вызываться несколькими пользователями. Моя "база данных" ConcurrentHashMap<String, Double>
, какая пара ключ / значение представляет собой идентификатор счета и его текущий баланс.
Я знаю, что отдельные операции ConcurrentHashMap (get()
, put()
и т. д.) являются поточно-ориентированными, но метод изъятия / депозита будет иметь несколько вызовов методов, что в конечном итоге сделает его не поточно-ориентированным.
Моя проблема: как сделать так, чтобы мои методы вывода / депозита были поточно-ориентированными? Сначала я думал о том, чтобы сделать их synchronized
, но это не имеет никакого смысла, так как я бы отбросил мелкозернистый встроенный механизм синхронизации ConcurrentHashMap
,
Это оба метода снятия и депозита (не беспокойтесь о Double
за деньги тут неактуально в этом контексте)
private void deposit(ConcurrentHashMap<String, Double> myDatabase, String fromAccountId, String toAccountId, double amount) {
if(myDatabase.get(fromAccountId) < amount) {
throw new MonetaryOperationViolation("Insufficient funds to perform this operation");
}
//Add the amount to the receiver's account
myDatabase.replace(toAccountId, myDatabase.get(toAccountId), c.get(toAccountId) + amount); //key, oldValue, newValue
//Withdraw it from the sender's account
withdraw(myDatabase, fromAccountId, amount);
}
private void withdraw(ConcurrentHashMap<String, Double> myDatabase, String accountId, double amount) {
if(myDatabase.get(accountId) < amount) {
throw new MonetaryOperationViolation("Insufficient funds to perform this operation");
}
myDatabase.replace(accountId, myDatabase.get(accountId), myDatabase.get(accountId) - amount);
}
Я надеюсь, что я ясно дал понять относительно моей проблемы. Любая помощь будет по достоинству оценена.
2 ответа
Я не думаю, что можно решить такую задачу, просто используя ConcurrentHashMap с атомарным типом.
Представьте себе случай, когда деньги с одного счета были переведены на другой. В этом случае вам нужно синхронизировать не один элемент карты, а две учетные записи одновременно. Это называется транзакцией. Итак, что вам нужно сделать, это реализовать транзакции. Транзакция должна заблокировать все затронутые учетные записи и освободить их после завершения.
В качестве другого варианта вы можете просто создать потокобезопасную очередь с транзакциями и выполнять все транзакции последовательно, и вам не понадобится ни ConcurrentHashMap, ни синхронизация, однако, вероятно, речь идет не о той части, которую вы пытаетесь изучить.
Внутренние компоненты Java имеют много решений для параллелизма, чтобы использовать правильное, вам нужно ответить на простой вопрос: что мое приложение делает большую часть времени? Операции чтения или записи?
В случае, если он выполняет запись (снятие / депозит), я бы рекомендовал использовать java.util.concurrent.atomic.DoubleAdder
экземпляр вместо Double
это обеспечит безопасность потока и увеличит пропускную способность вашего приложения в аспекте записи.
В общем, такого рода приложения подходят для актерской модели. Каждый аккаунт может быть представлен актером. Актер будет поддерживать несколько типов сообщений, таких как: снятие / депозит / итого. Каркас АККА - отличная реализация актерской модели.