Ответ 1
Нет. Операция +=
не является потокобезопасной. Это требует блокировки и/или правильной цепочки отношений "бывает раньше" для любого выражения, включающего назначение для общего поля или элемента массива, чтобы быть потокобезопасным.
(С полем, объявленным как volatile
, существуют отношения "произойдет-до"... но только при выполнении операций чтения и записи. Операция +=
состоит из чтения и записи. Это индивидуально атомарные, но последовательность не является. И большинство выражений присваивания с использованием =
включают как одно или несколько чтений (справа), так и запись. Эта последовательность также не является атомарной.)
Для полной истории прочитайте JLS 17.4... или соответствующую главу "Java Concurrency in Action" Брайана Гетца и др.
Как я знаю, основные операции над примитивными типами являются потокобезопасными...
Собственно, это неправильная предпосылка:
- рассмотрим случай массивов
- считают, что выражения обычно состоят из последовательности операций и что последовательность атомных операций не гарантируется как атомарная.
Существует дополнительная проблема для типа double
. JLS (17.7) говорит следующее:
"Для целей модели памяти языка программирования Java одна запись в энергонезависимое длинное или двойное значение рассматривается как две отдельные записи: по одной на каждую 32-битную половину. Это может привести к ситуации, когда поток видит первые 32 бита 64-битного значения из одной записи, а второй 32 бита из другой записи."
"Пишет и читает изменчивые длинные и двойные значения, всегда атомарные".
В комментарии вы спросили:
Какой тип я должен использовать, чтобы избежать глобальной синхронизации, которая останавливает все потоки внутри этого цикла?
В этом случае (где вы обновляете double[]
, нет никакой альтернативы синхронизации с блокировками или примитивными мьютексами.
Если у вас есть int[]
или long[]
, вы можете заменить их на AtomicIntegerArray
или AtomicLongArray
и использовать обновление без блокировки этих классов. Однако нет класса AtomicDoubleArray
или даже класса AtomicDouble
.
(UPDATE), кто-то указал, что Guava предоставляет класс AtomicDoubleArray
, поэтому это будет вариант. На самом деле хороший.)
Одним из способов избежать "глобальной блокировки" и массивных проблем с конфликтом может быть разделение массива на условные области, каждый со своей собственной блокировкой. Таким образом, одному потоку нужно только блокировать другой поток, если они используют одну и ту же область массива. (Отдельные записи/блокировки нескольких считывателей также могут помочь... если читается подавляющее большинство обращений.)