Насколько большая задержка при многопоточном доступе может быть при обновлении значения без volatile
Предыстория:
Есть объект(список объектов на самом деле), в одном из полей которого лежит HashMap.
Эта HashMap довольно часто перезаписывается (не значения в ней, а ссылка на объект - т.е. утрируя myObject.mapField = new HashMap(...))
Эта мапа довольно активно читается(только) из разных потоков.
При это мне НЕ важно, если какой-то поток будет использовать данные, допустим, секундной давности.
Суть вопроса:
Нужен ли мне тут volatile? Долго читал гугл(может плохо читал), но так и не смог найти информации, насколько большие задержки в принципе могут быть, если мы обычную, не рассчитанную на конкурентность переменную, меняем в одном потоке и читаем в другом?
Ответы (2 шт):
Если что-то закэшировано в L1/2/3 кэшах процессора, то оно там может оставаться вечно. Перечитать процессор может только если ему придётся очистить кэш для загрузки каких-то других данных, а потом ему потребуется прочитать этот map, то тогда он уже загрузит свежие данные.
Вот там в ответах даны приблизительные тайминги для чтения из кэша и из памяти
Т.е. можешь считать, что с volatile всегда будет 60-100нс. Не так много приложений, где это критично и я на 99.99999% уверен, что твоё не из них, потому что ты задаёшь вопросы довольно начального уровня.
Для оставшихся 0.00001% можно сделать так
volatile Map volatileMap = ...
void someLongRunningThread() {
Map localCopy = volatileMap;
Map lastSync = System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis() - lastSync > 1000) {
localCopy = volatileMap;
lastSync = System.currentTimeMillis();
}
// ... work with map
}
}
Если коротко, то без volatile ничего не гарантируется и задержки могут быть сколь угодно долгими, так как ничто в спецификации языка не требует синхронизации, и реализация может делать или не делать это на свое усмотрение. На конкретной реализации может и будет работать без volatile, но это непереносимо и может сломаться, например, при обновлении версии реализации.
Java Memory Model описывает, как будет производиться синхронизация, если точнее, то взаимосвязь видимости результатов операций в контексте, какие операции будут гарантированно видеть результаты других. То что не описано отдается на откуп реализации, т.е. компилятору и JVM. Т.е. и реализация, которая после каждой операции вставляет барьер памяти (очень неоптимальная, таких уверен нет), и та, которая вставляет барьеры в местах абсолютно необходимых (т.е. описанных в JMM), делая дополнительно переупорядочивание операций доступа к памяти (которые не нарушают JMM) будут соответствовать спецификации, но будут сильно отличаться производительностью.
Вот что говорится в спецификации:
An implementation is free to produce any code it likes, as long as all resulting executions of a program produce a result that can be predicted by the memory model.
This provides a great deal of freedom for the implementor to perform a myriad of code transformations, including the reordering of actions and removal of unnecessary synchronization.
Вещи которые соответствуют здравому смыслу (типа что результат записи в переменную виден далее по коду в том же потоке) покрыты JMM и этого достаточно, чтоб программа, которая выполняет все требования JMM работала предсказуемо, независимо от любых переупорядочиваний, барьеров и т.д.
В JMM не ничего, чтобы заставляло бы увидеть запись в не volatile переменную при чтении из другого потока "просто так" или "через какое-то время". Это требуется только в четко определенных случаях, если грубо то:
- первый поток записал в volatile, второй из нее прочитал
- первый поток отпустил монитор, второй захватил
- первый поток запустил второй
- первый поток завершился, второй дождался завершения первого
- первый поток послал interrupt, второй его получил
На практике, если у вас нет операций, которые делают что-то из вышеперечисленного, то частота синхронизации (и вообще будет ли такая синхронизация делаться) зависит от реализации и непереносимо. Видел не раз в Oracle и OpenJDK в классических случаях типа ожидания в цикле на не volatile переменной, что синхронизации не происходит никогда, и поток висит в цикле, после изменения наблюдаемой переменной другим потоком.