2014-05-03 15 views
26

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:

  1. Chúng tôi yêu cầu cho một Collections.synchronizedSet().
  2. 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();} 
        } 
    } 
    
  3. 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(...)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.)

+3

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

Trả lời

11

Khi bạn viết, đánh giá bằng cách triển khai, forEach() là an toàn chủ đề cho các bộ sưu tập được cung cấp với JDK (xem tuyên bố từ chối bên dưới) vì nó yêu cầu giám sát của mutex được mua để tiếp tục.

Đây cũng là chủ đề an toàn theo đặc điểm kỹ thuật?

Ý kiến ​​của tôi - không, và đây là giải thích. Collections.synchronizedXXX() javadoc, viết lại trong các từ ngắn, nói - "tất cả các phương thức đều an toàn chỉ trừ những phương thức được sử dụng để lặp qua nó".

Đối số khác, mặc dù rất chủ quan là những gì yshavit đã viết - trừ khi đã nói/đọc điều đó, hãy cân nhắc API/lớp/bất kỳ thứ gì không an toàn.

Bây giờ, hãy xem xét kỹ hơn các javadocs. Tôi đoán tôi có thể nói rằng phương pháp forEach() được sử dụng để lặp qua nó, vì vậy, theo lời khuyên từ javadoc, chúng ta nên xem xét nó không an toàn thread, mặc dù nó là đối diện với thực tế (thực hiện).

Dù sao, tôi đồng ý với tuyên bố của yshavit rằng tài liệu nên được cập nhật vì đây rất có thể là tài liệu, chứ không phải lỗ hổng triển khai. Nhưng, không ai có thể nói chắc chắn ngoại trừ các nhà phát triển JDK, hãy xem các mối quan tâm bên dưới.

Điểm cuối cùng tôi muốn đề cập trong phần thảo luận này - chúng tôi có thể giả định rằng bộ sưu tập tùy chỉnh có thể được gói với Collections.synchronizedXXX() và việc triển khai forEach() của bộ sưu tập này có thể ... có thể là bất kỳ thứ gì. Bộ sưu tập có thể thực hiện xử lý không đồng bộ các phần tử trong phương thức forEach(), sinh ra một chuỗi cho mỗi phần tử ... nó chỉ bị ràng buộc bởi trí tưởng tượng của tác giả và bọc đồng bộ (mutex) không thể đảm bảo an toàn luồng cho các trường hợp như vậy. Đó là vấn đề cụ thể có thể là lý do để không tuyên bố forEach() phương pháp như thread-safe ..

+2

Điều này đi ngược lại lời khuyên chuẩn về an toàn luồng, đó là nếu một cái gì đó không được tuyên bố rõ ràng là an toàn chỉ, bạn nên cho rằng nó không an toàn cho luồng. Bởi lý do của bạn, 'synchronSetSet.stream(). ForEach (...)' cũng nên được thread-an toàn, nhưng nó không phải là (như OP chỉ ra). – yshavit

+0

@yshavit Đối số của bạn thực sự tốt, nó làm tôi thay đổi quan điểm của tôi :) –

+0

Rất vui được trợ giúp và +1 ngay bây giờ. :) Tôi sẽ giữ bình luận của tôi, nếu không sao, trong trường hợp một googler tương lai đi qua điều này và sẽ được giúp đỡ bởi các bình luận bổ sung (và lời khuyên chung). – yshavit

5

Đó là giá trị để có một cái nhìn tại documentation of Collections.synchronizedCollection hơn Collections.synchronizedSet() như rằng tài liệu đã được làm sạch lên đã:

Điều bắt buộc là người sử dụng bằng tay đồng bộ hóa trên bộ sưu tập trở lại khi đi qua nó qua Iterator, Spliterator hoặc Stream: ...

Tôi nghĩ rằng, điều này làm cho nó khá rõ ràng rằng có một sự phân biệt giữa các iteration thông qua một đối tượng khác với đồng bộ Collection chính nó và sử dụng phương pháp forEach của nó.Nhưng ngay cả với từ ngữ cũ bạn có thể rút ra kết luận rằng có một sự phân biệt như vậy:

Điều bắt buộc là người sử dụng bằng tay đồng bộ hóa trên được trả về khi iterating trên nó: ...

(nhấn mạnh của tôi)

So với các documentation for Iterable.forEach:

Thực hiện hành động đã cho cho mỗi phần tử của Iterable cho đến khi tất cả các phần tử đã được xử lý hoặc hành động ném một ngoại lệ.

Trong khi rõ ràng với nhà phát triển rằng phải có một (nội bộ) lặp lại xảy ra để đạt được điều này, lặp lại này là chi tiết triển khai. Từ từ ngữ của đặc tả đã cho, đó chỉ là hành động (meta-) để thực hiện một hành động cho mỗi phần tử.

Khi sử dụng phương pháp đó, người dùng không iterating trên các yếu tố và do đó không chịu trách nhiệm về đồng bộ hóa được đề cập trong tài liệu hướng dẫn Collections.synchronized….

Tuy nhiên, đó là một chút tinh tế và tốt là documentation of synchronizedCollection liệt kê các trường hợp đồng bộ hóa thủ công một cách rõ ràng và tôi nghĩ rằng tài liệu của các phương pháp khác cũng phải được điều chỉnh.

Các vấn đề liên quan