Это правило многопоточности для 64-разрядных числовых переменных по-прежнему актуально в Java 1.8 или устарело?

Из Goetz, Peierls, Bloch et al. 2006: Java-параллелизм на практике

3.1.2. Неатомные 64-битные операции

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

Безопасность "из воздуха" распространяется на все переменные, за одним исключением: 64-разрядные числовые переменные (двойные и длинные), которые не объявлены как изменчивые (см. Раздел 3.1.4). Модель памяти Java требует, чтобы операции выборки и хранения были атомарными, но для энергонезависимых длинных и двойных переменных JVM разрешается обрабатывать 64-битное чтение или запись как две отдельные 32-битные операции. Если чтение и запись происходят в разных потоках, следовательно, возможно прочитать энергонезависимую длинную и вернуть старшие 32 бита одного значения и младшие 32 бита другого.[3]

Таким образом, даже если вам не нужны устаревшие значения, небезопасно использовать совместно изменяемые переменные long и double в многопоточных программах, если они не объявлены как volatile или защищены блокировкой.

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

Это было написано после выпуска Java 5, вышедшего в 2004 году, с множеством изменений, направленных на упрощение многопоточности и параллелизма. Итак, почему это все еще применяется? Даже сейчас, десятилетие спустя?

Если это только потому, что можно запускать приложения Java на 32-разрядном оборудовании, почему нет возможности JVM во время выполнения, чтобы разрешить ее при желании?

Разве не было бы полезно иметь возможность кодировать многопоточные приложения с низкими задержками, не беспокоясь об этом?

1 ответ

Решение

Это правило многопоточности для 64-разрядных числовых переменных по-прежнему актуально в Java 1.8 или устарело?

Да, это все еще верно, обратитесь к спецификации Java 8:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html

Для целей модели памяти языка программирования Java одна запись в энергонезависимое длинное или двойное значение рассматривается как две отдельные записи: по одной на каждую 32-битную половину. Это может привести к ситуации, когда поток видит первые 32 бита 64-битного значения из одной записи, а вторые 32 бита из другой записи.

Записывает и читает изменчивые длинные и двойные значения, всегда атомарные.

Запись и чтение ссылок всегда являются атомарными, независимо от того, реализованы ли они как 32-битные или 64-битные значения.


Итак, почему это все еще применяется?

Объяснение также в спецификации:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html

В некоторых реализациях может оказаться удобным разделить одно действие записи на 64-битном длинном или двойном значении на два действия записи на смежных 32-битных значениях. Ради эффективности это поведение зависит от реализации; Реализация виртуальной машины Java может выполнять запись в длинные и двойные значения атомарно или в две части.

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

Я думаю, это потому, что Java работает на многих моделях оборудования - например, на мобильных телефонах, маршрутизаторах, может быть, даже на холодильниках, стиральных машинах, пылесосах и т. Д., Которые могут быть оснащены крошечными 8- или 16-разрядными микропроцессорами. И спецификация Java является общей для всех из них.

Другие вопросы по тегам