Введение
Предположим, у меня есть синглтон ConcurrentHashMap:
public class RecordsMapSingleton {
private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();
public static ConcurrentHashMap<String, Record> getInstance() {
return payments;
}
}
Затем у меня есть три последующих запроса (все обрабатываются разными потоками) из разных источников.
Первая служба делает запрос, который получает синглтон, создает экземпляр Record
, генерирует уникальный идентификатор и помещает его в Map
, затем отправляет этот идентификатор в другую службу.
Затем вторая служба выполняет другой запрос с этим идентификатором. Он получает синглтон, находит экземпляр Record
и изменяет его.
Наконец (вероятно, через полчаса) вторая служба делает еще один запрос для дальнейшего изменения Record
.
Проблема
В очень редких случаях я обнаруживаю heisenbug. В журналах я вижу, что первый запрос успешно поместил Record
в Map
, второй запрос нашел его по идентификатору и изменил его, а затем третий запрос попытался найти запись по идентификатору, но ничего не нашел (get()
вернул null
).
Единственная вещь, которую я нашел о ConcurrentHashMap
гарантиях, это:
Действия в потоке перед помещением объекта в любую параллельную коллекцию происходят до действий, следующих за доступом или удалением этого элемента из коллекции в другом потоке.
из здесь. Если я понял это правильно, это буквально означает, что get()
может возвращать любое значение, которое когда-то было в Map, если это не портит happens-before
отношения между действиями в разных потоках.
В моем случае это применяется следующим образом : если третий запрос не заботится о том, что произошло во время обработки первого и второго, то он может читать null
из Map
.
Меня это не устраивает, потому что мне действительно нужно получить от Map
последний актуальный Record
.
Что я пробовал
Итак, я начал думать, как сформировать happens-before
отношения между последующими Map
модификациями; и пришла с идеей. JLS говорит (в 17.4.4), что:
Запись в изменчивую переменную v (§8.3.1.4) синхронизируется со всеми последующими чтениями v любым потоком (где «последующие» определены в соответствии с порядком синхронизации).
Итак, предположим, я изменю свой синглтон следующим образом:
public class RecordsMapSingleton {
private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();
private static volatile long revision = 0;
public static ConcurrentHashMap<String, Record> getInstance() {
return payments;
}
public static void incrementRevision() {
revision++;
}
public static long getRevision() {
return revision;
}
}
Затем после каждой модификации Map
или Record
внутри я вызываю incrementRevision()
, а перед любым чтением из карты я вызываю getRevision()
.
Вопрос strong >
Из-за природы вирусов heisenbug ни одного теста недостаточно, чтобы сказать, что это решение правильное. И я не эксперт в области параллелизма, поэтому не мог проверить это формально.
Может ли кто-нибудь одобрить, что следование этому подходу гарантирует, что я всегда буду получать последнее фактическое значение от ConcurrentHashMap
? Если этот подход неверен или кажется неэффективным, не могли бы вы порекомендовать мне что-нибудь еще?
T1: put(a); put(b)
,T2: get(b); get(a)
. Еслиget(b)
вернет ожидаемое значение,get(a)
также всегда вернет ожидаемое значение. нет? - person Kelvin Ng   schedule 21.04.2015put(b)
изменения какого-то изменчивого поля), но не уверен. И меня интересуют случаи, когда я не знаю ожидаемого значенияb
. - person mkrakhin   schedule 21.04.2015ConcurrentHashMap
: это связано с доступом кRecord
. Все его поля должны быть непостоянными или доступ к ним синхронизирован. В противном случае они подпадают под правило «не произошло раньше». - person user207421   schedule 21.04.2015String
, а результатget()
-null
, поэтому нет ничего внутри типаRecord
, которое могло бы вызвать проблему. - person Holger   schedule 21.04.2015get()
может вернуть любое значение, которое однажды хранилось под этим ключом; и затем он гарантирует, что любые действия, которыеhappened-before
помещают полученное значение вmap
happened-before
, получают это значение. - person mkrakhin   schedule 21.04.2015put
иget
с идентификатором хэш-кода объекта.System.identityHashCode(this)
Возвращаемое значение будет ссылкой на память. Я думаю, что это скорее что-то со ссылками, если не будет дополнительной мутации. - person John Vint   schedule 21.04.2015ConcurrentHashMap
переопределяетhashCode()
, поэтому это не ссылка на память, а хэш элементов внутри. В любом случае, я мог сравнитьhashCode
после записи и перед следующим чтением, но когда я попытался это сделать, все работало, как ожидалось, долгое время, и у меня не было никаких ошибок :( - person mkrakhin   schedule 21.04.2015System.identityHashCode
, который всегда будет давать вам ссылку на объект в памяти. Используйте System.identityHashCode - person John Vint   schedule 21.04.2015cloudlets
, поэтому я не мог быть уверен, что физически это действительно один сервер). - person mkrakhin   schedule 21.04.2015