2015-09-15 19 views

Trả lời

15

@Eran đã explained làm thế nào để giải quyết vấn đề này tốt hơn. Tôi sẽ giải thích lý do tại sao ConcurrentModificationException xảy ra.

ConcurrentModificationException xảy ra do bạn đang sửa đổi nguồn luồng. Map của bạn có thể là HashMap hoặc TreeMap hoặc bản đồ không đồng thời khác. Giả sử đó là HashMap. Mỗi luồng được hỗ trợ bởi Spliterator. Nếu spliterator không có IMMUTABLECONCURRENT đặc điểm, sau đó, như tài liệu nói:

Sau khi gắn một Spliterator nên, trên cơ sở nỗ lực tốt nhất, ném ConcurrentModificationException nếu can thiệp về cấu trúc được phát hiện. Trình tách liên kết thực hiện việc này được gọi là không nhanh.

Vì vậy, các HashMap.keySet().spliterator() không phải là IMMUTABLE (vì Set này có thể được sửa đổi) và không CONCURRENT (cập nhật đồng thời không an toàn cho HashMap). Vì vậy, nó chỉ phát hiện những thay đổi đồng thời và ném một số tài liệu quy định là ConcurrentModificationException.

Ngoài ra nó có giá trị trích dẫn tài liệu HashMap:

Các vòng lặp được trả về bởi tất cả các "phương pháp xem bộ sưu tập" của lớp này là thất bại nhanh: nếu bản đồ được cấu trúc biến đổi bất cứ lúc nào sau khi iterator được tạo ra, bằng bất kỳ cách nào ngoại trừ thông qua phương thức remove của chính trình lặp, trình lặp sẽ ném một ConcurrentModificationException. Do đó, khi đối mặt với sửa đổi đồng thời, trình vòng lặp không nhanh chóng và sạch sẽ, thay vì mạo hiểm hành vi tùy ý, không xác định tại một thời điểm không xác định trong tương lai.

Lưu ý rằng hành vi không nhanh của trình lặp không thể được đảm bảo vì nói chung, không thể thực hiện bất kỳ đảm bảo cứng nào khi có sự sửa đổi đồng thời không đồng bộ. Trình vòng lặp không nhanh chóng ném ConcurrentModificationException trên cơ sở tốt nhất. Do đó, sẽ sai khi viết một chương trình phụ thuộc vào ngoại lệ này cho tính chính xác của nó: hành vi không nhanh của các trình lặp nên chỉ được sử dụng để phát hiện lỗi.

Trong khi chỉ nói về trình biến lặp, tôi tin rằng điều này cũng giống nhau đối với trình phân tách.

+0

Tôi nghĩ đây là câu trả lời hay nhất, nhưng bạn có thể sửa nó để thêm giải pháp mà @Eran đã đề cập. Nó sẽ là 100% đáp ứng cho bất cứ ai có cùng một vấn đề trong tương lai. – jaskmar

+1

@ MariuszJaskółka, câu trả lời Eran cũng ở đây và những người khác cũng có khả năng sẽ thấy nó. Đó là chính xác và tôi upvoted nó. Tôi có thể thêm một tham chiếu đến giải pháp của anh ấy. –

8

Bạn không cần API Stream cho điều đó. Sử dụng retainAll trên keySet. Mọi thay đổi trên Set được trả lại bởi keySet() được phản ánh trong bản gốc Map.

someMap.keySet().retainAll(someList); 
+0

OK, đó là câu trả lời hay cho câu hỏi thứ hai của tôi. Nhưng tôi vẫn không biết tại sao 'java.util.ConcurrentModificationException' lại xảy ra. – jaskmar

3

gọi dòng của bạn là (logic) làm giống như:

for (K k : someMap.keySet()) { 
    if (!someList.contains(k)) { 
     someMap.remove(k); 
    } 
} 

Nếu bạn chạy này, bạn sẽ tìm thấy nó ném ConcurrentModificationException, bởi vì nó được sửa đổi bản đồ cùng một lúc như bạn đang lặp lại nó. Nếu bạn có một cái nhìn tại docs, bạn sẽ nhận thấy những điều sau đây:

Lưu ý rằng ngoại lệ này không phải lúc nào cũng chỉ ra rằng một đối tượng đã được đồng thời sửa đổi bởi một chuỗi khác nhau. Nếu một luồng đơn phát ra một chuỗi các lời gọi phương thức vi phạm hợp đồng của một đối tượng, đối tượng có thể ném ngoại lệ này. Ví dụ, nếu một chủ đề sửa đổi một bộ sưu tập trực tiếp trong khi nó đang lặp qua bộ sưu tập với một trình lặp không nhanh, thì trình vòng lặp sẽ ném ngoại lệ này.

Đây là những gì bạn đang làm, việc triển khai bản đồ bạn đang sử dụng rõ ràng có trình lặp không nhanh, do đó ngoại lệ này đang bị ném.

Một thay thế có thể là để loại bỏ các mục bằng cách sử dụng iterator trực tiếp:

for (Iterator<K> ks = someMap.keySet().iterator(); ks.hasNext();) { 
    K next = ks.next(); 
    if (!someList.contains(k)) { 
     ks.remove(); 
    } 
} 
Các vấn đề liên quan