#java #multithreading #concurrency #happens-before
#java #многопоточность #параллелизм #происходит — до
Вопрос:
Проходя через параллелизм Java на практике Брайана Гетца, я столкнулся со следующей строкой:
Скачок данных возникает, когда переменная считывается более чем одним потоком и записывается по крайней мере одним потоком, но чтения и записи не упорядочены по happens-before. Правильно синхронизированная программа — это программа, в которой нет скачков данных; правильно синхронизированные программы демонстрируют последовательную согласованность, что означает, что все действия в программе, по-видимому, выполняются в фиксированном глобальном порядке.
Мой вопрос в том, является ли не по порядку запись единственной причиной состояния гонки данных в Java или, возможно, в других языках программирования?
Обновить
Хорошо, я провел еще некоторое расследование о скачке данных и нашел следующее с официального сайта Oracle, в котором говорится, что :
Анализатор потоков обнаруживает скачки данных, которые происходят во время выполнения многопоточного процесса. Гонка данных происходит, когда:
- два или более потоков в одном процессе одновременно обращаются к одной и той же ячейке памяти и
- по крайней мере, один из обращений предназначен для записи, и
- потоки не используют никаких эксклюзивных блокировок для контроля своих обращений к этой памяти.
Когда выполняются эти три условия, порядок обращений не является детерминированным, и вычисления могут давать разные результаты от запуска к запуску в зависимости от этого порядка. Некоторые скачки данных могут быть безвредными (например, когда доступ к памяти используется для ожидания занятости), но многие скачки данных являются ошибками в программе.
В этой части упоминается, что: порядок обращений не является детерминированным
Это говорит о последовательности, в которой потоки обращаются к ячейке памяти? Если да, то синхронизация никогда не гарантирует порядок, в котором потоки будут обращаться к блоку кода. Итак, как синхронизация может решить проблему гонки данных?
Комментарии:
1. Да, может быть. Проблема заключается в видимости памяти. Если вы не пометили переменную как volatile или не защищаете доступ к ней синхронизированным блоком или блокировкой, возможно, что данный поток не увидит обновления, сделанные другим потоком.
2. @Mac: запись в эту переменную атомарно является ключевой. Атомарные операции, поддерживаемые Java (например,
java.util.concurrent.atomic.*
), гарантируют, что операции упорядочены. Итак, если один поток обновляет атомарную переменную, обновление происходит — перед всеми последующими чтениями. Сама атомарная операция устанавливает барьер памяти. Если вы не можете обновить переменную атомарно (или с помощью синхронизации или блокировок), то могут произойти случаи, подобные упомянутым в комментарии @BrettOkken3. @Mac: все дело в барьере памяти. Атомарная операция, видимость, синхронизация и блокировки — все это связано с установкой барьера памяти, чтобы операции с определенными блоками данных имели надлежащую семантику упорядочения.
4. Нам нужно быть осторожными, говоря об атомарных операциях. Установка любого примитива (кроме double или long) является атомарной. Это не означает, что это видно во всех потоках.
5. @jameslarge Эти два не обязательно конфликтуют. Некоторые скачки данных действительно могут быть доброкачественными, но программа все равно будет классифицироваться как неправильно синхронизированная.
Ответ №1:
Я бы предпочел определить гонку данных как
Гонка данных между записью и чтением некоторого значения или ссылки из переменной — это ситуация, когда результат чтения определяется «внутренним» (управляемым jvm или os) планированием потоков.
Фактически, второе определение из вопроса говорит то же самое более «официальными» словами 🙂
Другими словами, рассмотрим поток A, записывающий некоторое значение в переменную, и поток B, пытающийся его прочитать. Если вы пропустите какой-либо вид синхронизации (или другой механизм, который может обеспечить гарантии «произойдет до» между записью и последующим чтением), в вашей программе происходит гонка данных между потоками A и B.
Теперь, к вашему вопросу:
Это говорит о последовательности, в которой потоки обращаются к ячейке памяти? Если да, то синхронизация никогда не гарантирует порядок, в котором потоки будут обращаться к блоку кода.
Синхронизация в этом конкретном случае гарантирует, что вы никогда не сможете прочитать значение, которое имела переменная до того, как поток записи записал новое значение после выхода потока записи из synchronized
блока или метода. Без синхронизации есть шанс прочитать старое значение даже после того, как запись действительно произошла.
О порядке доступа: он будет детерминированным с синхронизацией следующим образом:
Давайте еще раз взглянем на наши потоки A и B. Порядок операций теперь последовательный — поток B не сможет начать чтение, пока поток A не закончит запись. Чтобы прояснить эту ситуацию, представьте, что запись и чтение — это действительно длительный процесс. Без синхронизации эти операции смогут пересекаться друг с другом, что может привести к считыванию некоторых бессмысленных значений.