Программирование JavaМногопоточностьJava-разработчик серверных систем

При чтении обычного поля long одним потоком во время записи этого поля другим потоком возможна ли атомарная...

При чтении обычного поля long одним потоком во время записи этого поля другим потоком возможна ли атомарная ошибка чтения?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Да. Для обычного, то есть не volatile, поля long Java Memory Model не гарантирует атомарность чтения и записи: поток теоретически может увидеть значение, составленное из частей двух разных записей. Для volatile long, а также для доступа под общим lock такая ситуация не допускается.

Исторический контекст

Проблема связана с тем, что long занимает 64 бита, а некоторые аппаратные платформы и виртуальные машины могут выполнять доступ к нему несколькими машинными операциями. Поэтому спецификация Java отдельно оговаривает атомарность операций над 64-битными значениями.

Современные 64-битные JVM обычно выполняют такие операции атомарно на практике, но это свойство конкретной реализации или платформы нельзя использовать как гарантию программы. Гарантию должен давать контракт Java Memory Model.

Постановка проблемы

Если один поток публикует изменяющееся обычное поле long, а другой читает его без синхронизации, возникают два независимых риска: читатель может долго видеть устаревшее значение и может получить разорванное значение, состоящее из частей разных записей.

Разорванное чтение особенно опасно для временных меток, идентификаторов, битовых масок и денежных значений: результат может выглядеть правдоподобно, но фактически никогда не записывался ни одним потоком.

Подробное решение

Для обычного long JMM не предоставляет гарантии атомарности доступа. Это не означает, что разорванное чтение будет происходить регулярно; оно означает, что программа не вправе рассчитывать на его невозможность.

Объявление поля volatile устанавливает две необходимые гарантии: чтение и запись такого значения атомарны, а запись одного потока становится видимой чтению другого при соблюдении соответствующей связи volatile. Однако volatile не превращает последовательность «прочитать—изменить—записать» в атомарную операцию.

Если значение только публикуется и заменяется целиком, обычно достаточно volatile long. Если нужны атомарные изменения вроде увеличения или условного обновления, применяют AtomicLong либо блокировку. Если long является частью нескольких согласованных полей, предпочтительнее защищать весь инвариант одним lock, а не синхронизировать поля по отдельности.

Синхронизация только чтения или только записи недостаточна: все конкурирующие доступы к полю должны использовать совместимый механизм. Случайная атомарность на конкретной JVM не заменяет требования JMM.

Ситуация из практики

Сервис собирает статистику и записывает в общее поле 64-битную метку версии конфигурации. Потоки чтения должны видеть только целые версии, а обновление происходит заменой значения.

Можно оставить поле обычным: это не требует затрат, но не даёт ни гарантии видимости, ни гарантии атомарности. Можно использовать AtomicLong: это обеспечивает атомарные операции и подходит, если версию нужно увеличивать или сравнивать-и-заменять, но добавляет более специализированный API.

Выбран вариант с volatile long, поскольку значение заменяется целиком, а составная операция над ним не требуется. В результате читатели получают атомарное значение и гарантированную видимость последней произошедшей записи; если позднее появится логика условного обновления, поле следует заменить на AtomicLong или защищать операцию блокировкой.

Что кандидаты часто упускают

  1. Достаточно ли volatile для атомарного увеличения long?

Нет. volatile защищает отдельные чтение и запись, но увеличение состоит как минимум из чтения старого значения, вычисления нового и записи. Два потока могут прочитать одно старое значение и потерять одно из обновлений. Для такого сценария нужен AtomicLong.incrementAndGet() или lock.

  1. Может ли разорванное чтение вернуть значение, которое никто не записывал?

Да, именно это является главным смыслом отсутствия гарантии атомарности. Если части двух записей попадут в один результат, комбинация битов может не совпадать ни с одним фактически записанным long. Нельзя ограничиваться проверкой результата на равенство только старому или только новому значению.

  1. Устраняет ли блокировка проблему, если запись защищена lock, а чтение выполняется без него?

Нет. Для гарантии нужно, чтобы чтение и запись использовали один и тот же lock либо другой корректный механизм синхронизации. Иначе читатель не образует необходимой связи happens-before с записью и не получает гарантии ни видимости, ни согласованной атомарности доступа.