Như chúng ta đã biết, lặp lại trên một bộ sưu tập đồng thời không phải là thread an toàn theo mặc định, vì vậy người ta không thể sử dụng:Lặp lại qua Collections.synchronizedSet (...). ForEach() có đảm bảo là luồng an toàn không?
Set<E> set = Collections.synchronizedSet(new HashSet<>());
//fill with data
for (E e : set) {
process(e);
}
Điều này xảy ra khi dữ liệu có thể được thêm vào trong quá lặp, vì không có khóa độc quyền trên set
.
Đây là mô tả trong javadoc của Collections.synchronizedSet
:
public static Set synchronizedSet (Set s)
Trả về một đồng bộ set (thread-safe) được hỗ trợ bởi các thiết lập quy định. Để đảm bảo truy cập nối tiếp, điều quan trọng là tất cả quyền truy cập vào bộ sao lưu được thực hiện thông qua tập hợp trả về.
Điều bắt buộc là người sử dụng bằng tay đồng bộ hóa trên các thiết lập trở lại khi iterating trên nó:
Set s = Collections.synchronizedSet (HashSet mới());
...
synchronized (s) { Iterator i = s.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); }
Thất bại trong việc làm theo lời khuyên này có thể dẫn đến hành vi không xác định.
Tuy nhiên, điều này không áp dụng đối với Set.forEach
, mà kế thừa phương pháp mặc định forEach
từ Iterable.forEach.
Bây giờ tôi nhìn vào mã nguồn, và ở đây chúng ta có thể thấy rằng chúng tôi có cơ cấu như sau:
- Chúng tôi yêu cầu cho một
Collections.synchronizedSet()
. Chúng tôi nhận được một:
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();} } }
Nó mở rộng
SynchronizedCollection
, trong đó có các thú phương pháp sau đây bên cạnh những cái rõ ràng:// 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! }
Các mutex
sử dụng ở đây là cùng một đối tượng mà trong đó tất cả các hoạt động của Collections.synchronizedSet
khóa.
Bây giờ chúng ta có thể, xét xử của các thực hiện nói rằng nó là chủ đề an toàn để sử dụng Collections.synchronizedSet(...).forEach(...)
, nhưng nó cũng chủ đề an toàn bởi đặc điểm kỹ thuật?
(Đủ gây nhầm lẫn, Collections.synchronizedSet(...).stream().forEach(...)
là không luồng an toàn bằng cách triển khai và phán quyết của đặc điểm kỹ thuật dường như không xác định là tốt.)
Tìm tốt! Nó không xuất hiện để được thread-an toàn bởi spec, nhưng nó âm thanh như spec (cụ thể, cho phương pháp đó) nên được cập nhật. – yshavit