Правильно ли я понимаю, что следующий фрагмент кода безопасен с точки зрения публикации стандартной коллекции, которая по своей сути не является потокобезопасной (HashSet
в этом примере), потому что ни один из потоков не изменяет коллекцию после барьера памяти:
class C {
private final Set<String> strings;
C(final Set<String> strings) {
this.strings = new HashSet<>(strings);
}
void doSmthAsync() {
new Thread() {
@Override
public void run() {
for (final String s : strings) {
System.out.println(s);
}
}
}.start(); // Disregard the 2nd memory barrier caused by Thread.start()
}
}
а ниже нет?
class C {
private final Set<String> strings = new HashSet<>();
C(final Set<String> strings) {
this.strings.addAll(strings);
}
void doSmthAsync() {
new Thread() {
@Override
public void run() {
for (final String s : strings) {
System.out.println(s);
}
}
}.start(); // Disregard the 2nd memory barrier caused by Thread.start()
}
}
Обновление: приведенные выше примеры не совсем корректны, поскольку на самом деле существует два барьера памяти, а не один:
- инициализация конечного поля
Thread.start()
Если вместо этого в методе doSmthAsync()
мы передаем коллекцию уже запущенному потоку (например, одному из фонового пула потоков), кажется, что поток-потребитель может увидеть финальное поле в том состоянии, в котором оно было во время инициализации (т. е. пустой набор в 2-й случай).