#java #concurrency
Вопрос:
У меня есть следующие две структуры данных:
// Session -> Symbol -> ID
Map<String, Map<String, Long>> mapping1;
// Symbol -> ID -> Level -> Value
Map<String, Map<Long, Map<Integer, Double>>> mapping2;
и следующие методы:
void addSession(String session, String symbol, Long id) {
mapping1.computeIfAbsent(session, k -> new HashMap<>())
.put(symbol, id);
}
void removeSession(String session, String symbol, Long id); // similar to above
void addValue(String symbol, Long id, Integer level, Double value) {
mapping2.computeIfAbsent(symbol, k -> new HashMap<>())
.computeIfAbsent(id, k -> new HashMap<>())
.put(level, value);
}
Double getValue(String session, String symbol, Integer level) {
int id = mapping1.get(session).get(symbol);
return mapping2.get(symbol).get(id).get(level);
}
Теперь проблема в том, что у нас есть 1 поток, который выполняет всю запись (add/removeSession и AddValue), и 1 поток, который выполняет все чтение (GetValue). Записи сильно перевешивают чтение.
Ожидаемое количество вызовов:
- добавление/удаление сеанса — несколько раз в день
- Значение добавления — ~100 в секунду
- GetValue — каждые несколько секунд
Учитывая это, мне интересно, как лучше всего синхронизировать эти 2 потока без использования блокировки? Я пытаюсь придумать способ сгладить все данные в единую карту, но, похоже, ничего не могу придумать.
ИЗМЕНИТЬ: чтобы внести ясность. Мы хотим синхронизировать доступ к значению в отображении2. Это может быть обновлено 100 раз в секунду.
Комментарии:
1. видимость @kerberos84?
2. @kerberos84 отредактировано, чтобы было понятнее
Ответ №1:
Если у вас есть несколько потоков, обращающихся к одним и тем же данным, не имеет значения, как часто это происходит в день. Либо вы можете на 100% гарантировать, что они никогда не будут читать/писать одновременно, либо вы не можете. Между ними ничего нет.
Самым простым решением было бы использовать a ConcurrentHashMap
. Да, это повредит производительности, но действительно ли это имеет значение в вашем приложении? Попробуйте и оцените. В большинстве случаев это не имеет значения.
Комментарии:
1. это используется в горячем пути торгового приложения, поэтому производительность имеет решающее значение.
2. Но все же: «критично» не означает, что у вас есть узкое место. Другим способом может быть разделение ваших данных: не используйте один
Map
для всех данных, а разделите их на несколькоMap
. Например, создайте отдельнуюMap
для каждой первой буквыSymbol
(= ключ раздела). Это, конечно, зависит от ваших данных, можете ли вы их вообще разделить или есть ли какой-либо хороший ключ раздела для распределения нагрузки.3. Самым простым решением было бы создать методы
synchronized
. 100 раз в секунду-это довольно низкая скорость, которая не требует сложных подходов, с которыми ОП не знаком. На самом деле, при такой низкой вероятности разногласий, скорее всего, нет заметной разницы в производительности.
Ответ №2:
- используйте классы в пакете java.util.concurrent. В частности, ConcurrentHashMap. Он будет иметь внутренние блокировки для обеспечения целостности данных.
- Добавьте блоки синхронизации в GetValue() и добавьте значение() с ключом для сопоставления 2.
или
- Вы можете попробовать использовать ключевое слово volatile для переменных-членов.
Комментарии:
1. Является ли ваше предложение (3) сделать переменные mapping1 и mapping2 изменчивыми ? В таком случае это не поможет. Ключевое слово volatile гарантирует, что ссылка на карту поступает из основной памяти. Это оно. Это не дает никаких других гарантий, поэтому, если ваша карта
computeIfAbsent
небезопасна для потоков, volatile волшебным образом не делает ее потокобезопасной.