Как сделать потокобезопасным основанный на 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 это обеспечит безопасность потока и увеличит пропускную способность вашего приложения в аспекте записи.

В общем, такого рода приложения подходят для актерской модели. Каждый аккаунт может быть представлен актером. Актер будет поддерживать несколько типов сообщений, таких как: снятие / депозит / итого. Каркас АККА - отличная реализация актерской модели.

Другие вопросы по тегам