В этом примере все будет хорошо (хм, давайте немного приостановим суждение).Неизменность - это амброзия, когда речь заходит о безопасности потоков - если значение не может измениться, большинство проблем параллелизма сразу перестают вызывать беспокойство.
Амир упоминается volatile
, что обычно полезно- но конструктор также имеет аналогичную семантику для final
переменных, которые обеспечивают видимость.Подробности см. В JLS разделе 17.5 - по сути, конструктор формирует отношение случай-до между записью в конечные переменные и любыми последующими чтениями.
РЕДАКТИРОВАТЬ: Таким образом, вы устанавливаете ссылку values на массив в конструкторе, он виден во всех потоках в этой точке и затем не изменяется.Итак, мы знаем, что все остальные потоки будут видеть тот же массив .Но как насчет содержимого массива ?
В сущности, элементы массива не имеют какой-либо особой семантики в отношении волатильности, они как если бы вы только что объявили класс себе как-то так::
public class ArrayTen {
private int _0;
private int _1;
// ...
private int _9;
public int get(int index) {
if (index == 0) return _0;
// etc.
}
}
Итак, другой поток увидит эти переменные только в том случае, если мы сможем что-то сделать для установления отношения случай-до .И если мое понимание верно, это требует лишь небольшого изменения вашего исходного кода.
Мы уже знаем, что установка ссылки на массив происходит до конца конструктора.Дополнительным моментом, который всегда верен, является то, что действия в одном потоке происходят до более поздние действия в этом же потоке.Таким образом, мы можем объединить их, установив сначала поля массива, а затем , затем , назначив окончательное поле, чтобы получить эту транзитивную гарантию видимости.Конечно, для этого потребуется временная переменная:
public class ImmutableIntArray {
private final int[] array;
public ImmutableIntArray() {
int[] tmp = new int[10];
for (int i = 0; i < 10; i++) {
tmp[i] = i;
}
array = tmp;
}
// get() etc.
}
Я думаю, что это гарантированно безопасно, теперь, когда мы изменили, казалось бы, нерелевантный порядок присваивания и заполнения.
Но сноваможет быть что-то еще, что я пропустил, что означает, что гарантии параллелизма не так надежны, как хотелось бы.Этот вопрос, на мой взгляд, является отличным примером того, почему писать многопоточный код непросто, даже если вы думаете, что делаете что-то очень простое, и как нужно много думать и осторожно (а затем и исправлять ошибки), чтобы исправить ситуацию.