Гарантируется ли итерация через Collections.synchronizedSet (…) .forEach () поточно-ориентированной?
Как мы знаем, перебор параллельной коллекции не является потокобезопасным по умолчанию, поэтому нельзя использовать:
Set<E> set = Collections.synchronizedSet(new HashSet<>());
//fill with data
for (E e : set) {
process(e);
}
Это происходит потому, что данные могут быть добавлены во время итерации, потому что нет эксклюзивной блокировки наset
.
Это описать вJavadoc изCollections.synchronizedSet
:
общедоступный статический Set synchronizedSet (Set s)
Возвращает синхронизированный (потокобезопасный) набор, поддерживаемый указанным набором. Чтобы гарантировать последовательный доступ, очень важно, чтобы весь доступ к вспомогательному набору осуществлялся через возвращенный набор.
Крайне важно, чтобы пользователь вручную синхронизировал возвращаемый набор при его итерации:
Set s = Collections.synchronizedSet (new HashSet ());
...synchronized (s) { Iterator i = s.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); }
Несоблюдение этого совета может привести к недетерминированному поведению.
тем не мениеэто не относится кSet.forEach
, который наследует метод по умолчаниюforEach
отIterable.forEach.
Теперь я посмотрел исходный код, и здесь мы можем видеть, что у нас есть следующая структура:
Мы просимCollections.synchronizedSet()
.Мы получаем один:
public static <T> Set<T> synchronizedSet(Set<T> s) {
return new SynchronizedSet<>(s);
}
...
static class SynchronizedSet<E>
extends SynchronizedCollection<E>
implements Set<E> {
private static final long serialVersionUID = 487447009682186044L;
SynchronizedSet(Set<E> s) {
super(s);
}
SynchronizedSet(Set<E> s, Object mutex) {
super(s, mutex);
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return c.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return c.hashCode();}
}
}
РасширяетсяSynchronizedCollection
, который имеет следующееинтересно методы рядом с очевидными:
// Override default methods in Collection
@Override
public void forEach(Consumer<? super E> consumer) {
synchronized (mutex) {c.forEach(consumer);}
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
synchronized (mutex) {return c.removeIf(filter);}
}
@Override
public Spliterator<E> spliterator() {
return c.spliterator(); // Must be manually synched by user!
}
@Override
public Stream<E> stream() {
return c.stream(); // Must be manually synched by user!
}
@Override
public Stream<E> parallelStream() {
return c.parallelStream(); // Must be manually synched by user!
}
mutex
здесь используется тот же объект, к которомувсе операцииCollections.synchronizedSet
Блокировка
Теперь мы можем, судя пореализация сказать, что это потокобезопасное использованиеCollections.synchronizedSet(...).forEach(...)
, но это также потокобезопасныйпо спецификации?
(Достаточно запутанно,Collections.synchronizedSet(...).stream().forEach(...)
являетсяне Потокобезопасен при реализации, и вердикт спецификации, кажется, также неизвестен.)