Sửa đổi một Collection
khi iterating qua đó Collection
bằng cách sử dụng một Iterator
là không được phép bởi hầu hết các Collection
lớp học. Thư viện Java gọi một nỗ lực để sửa đổi Collection
trong khi lặp lại thông qua đó là "sửa đổi đồng thời". Thật không may, điều đó cho thấy nguyên nhân duy nhất có thể là sửa đổi đồng thời bởi nhiều luồng, nhưng thực tế không phải vậy. Chỉ sử dụng một luồng, có thể tạo một trình vòng lặp cho Collection
(sử dụng Collection.iterator()
hoặc nâng caofor
vòng lặp ), bắt đầu lặp (sử dụng Iterator.next()
hoặc nhập tương đối vào phần thân của for
vòng lặp nâng cao ), sửa đổiCollection
, sau đó tiếp tục lặp lại.
Để giúp các lập trình viên, một số triển khai của các Collection
lớp đó cố gắng phát hiện sửa đổi đồng thời sai lầm và ném ConcurrentModificationException
nếu họ phát hiện ra nó. Tuy nhiên, nói chung là không thể và thực tế để đảm bảo phát hiện tất cả các sửa đổi đồng thời. Vì vậy, việc sử dụng sai Collection
không phải lúc nào cũng dẫn đến ném ConcurrentModificationException
.
Các tài liệu ConcurrentModificationException
nói:
Ngoại lệ này có thể được ném bởi các phương thức đã phát hiện sửa đổi đồng thời của một đối tượng khi sửa đổi đó không được phép ...
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 sửa đổi đồng thời bởi một luồng khác. Nếu một luồng duy nhất đưa ra một chuỗi các lệnh gọi phương thức vi phạm hợp đồng của một đối tượng, thì đối tượng đó có thể ném ngoại lệ này ...
Lưu ý rằng hành vi không nhanh 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ửa đổi đồng thời không đồng bộ. Ném hoạt động nhanhConcurrentModificationException
trên cơ sở nỗ lực tốt nhất.
Lưu ý rằng
Các tài liệu của HashSet
, HashMap
, TreeSet
và ArrayList
các lớp học nói điều này:
Các trình vòng lặp được trả về [trực tiếp hoặc gián tiếp từ lớp này] không nhanh: nếu [bộ sưu tập] bị sửa đổi bất cứ lúc nào sau khi trình lặp được tạo, bằng bất kỳ cách nào ngoại trừ thông qua phương thức loại bỏ của trình lặp, nó Iterator
sẽ ném mộtConcurrentModificationException
. Do đó, khi đối mặt với sửa đổi đồng thời, iterator thất bại nhanh chóng và sạch sẽ, thay vì mạo hiểm hành vi độc đoán, 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ửa đổi đồng thời không đồng bộ. Lặp đi lặp lại nhanh thất bại ConcurrentModificationException
trên cơ sở nỗ lực tốt nhất. Do đó, sẽ là sai lầm khi viết một chương trình phụ thuộc vào ngoại lệ này vì tính chính xác của nó: hành vi không nhanh của các trình vòng lặp chỉ nên được sử dụng để phát hiện lỗi .
Một lần nữa lưu ý rằng hành vi "không thể được đảm bảo" và chỉ "trên cơ sở nỗ lực cao nhất".
Tài liệu về một số phương pháp của Map
giao diện nói lên điều này:
Việc triển khai không đồng thời sẽ ghi đè phương thức này và, trên cơ sở nỗ lực cao nhất, hãy ném ConcurrentModificationException
nếu phát hiện thấy hàm ánh xạ sửa đổi bản đồ này trong quá trình tính toán. Việc triển khai đồng thời sẽ ghi đè phương thức này và, trên cơ sở nỗ lực cao nhất, hãy ném IllegalStateException
nếu phát hiện ra rằng hàm ánh xạ sửa đổi bản đồ này trong quá trình tính toán và kết quả là tính toán sẽ không bao giờ hoàn thành.
Một lần nữa lưu ý rằng chỉ cần có "cơ sở nỗ lực tốt nhất" để phát hiện và chỉ ConcurrentModificationException
được đề xuất rõ ràng cho các lớp không đồng thời (không an toàn luồng).
Gỡ lỗi ConcurrentModificationException
Vì vậy, khi bạn thấy dấu vết ngăn xếp do a ConcurrentModificationException
, bạn không thể ngay lập tức cho rằng nguyên nhân là không an toàn khi truy cập đa luồng vào a Collection
. Bạn phải kiểm tra theo dõi ngăn xếp để xác định lớp nào Collection
đã ném ngoại lệ (một phương thức của lớp sẽ ném trực tiếp hoặc gián tiếp) và cho Collection
đối tượng nào. Sau đó, bạn phải kiểm tra xem đối tượng có thể được sửa đổi từ đâu.
- Nguyên nhân phổ biến nhất là sửa đổi
Collection
trong for
vòng lặp nâng cao trên Collection
. Chỉ vì bạn không thấy một Iterator
đối tượng trong mã nguồn của mình không có nghĩa là không Iterator
có ở đó! May mắn thay, một trong những tuyên bố của for
vòng lặp bị lỗi thường sẽ nằm trong theo dõi ngăn xếp, vì vậy việc theo dõi lỗi thường rất dễ dàng.
- Một trường hợp phức tạp hơn là khi mã của bạn chuyển xung quanh các tham chiếu đến
Collection
đối tượng. Lưu ý rằng các chế độ xem không thể thay đổi của các bộ sưu tập (như được sản xuất bởi Collections.unmodifiableList()
) giữ lại một tham chiếu đến bộ sưu tập có thể sửa đổi, do đó, việc lặp lại bộ sưu tập "không thể thay đổi" có thể ném ngoại lệ (việc sửa đổi đã được thực hiện ở nơi khác). Các chế độ xem khác của bạn Collection
, chẳng hạn như danh sách phụ , Map
bộ mục nhập và Map
bộ khóa cũng giữ lại các tham chiếu đến bản gốc (có thể sửa đổi) Collection
. Đây có thể là một vấn đề ngay cả đối với một chủ đề an toàn Collection
, chẳng hạn như CopyOnWriteList
; đừng cho rằng các bộ sưu tập an toàn luồng (đồng thời) không bao giờ có thể ném ngoại lệ.
- Những hoạt động có thể sửa đổi một
Collection
có thể là bất ngờ trong một số trường hợp. Ví dụ, LinkedHashMap.get()
sửa đổi bộ sưu tập của nó .
- Các trường hợp khó nhất là khi ngoại lệ là do sửa đổi đồng thời bởi nhiều luồng.
Lập trình để ngăn ngừa lỗi sửa đổi đồng thời
Khi có thể, giới hạn tất cả các tham chiếu đến một Collection
đối tượng, vì vậy việc ngăn chặn sửa đổi đồng thời sẽ dễ dàng hơn. Tạo Collection
một private
đối tượng hoặc một biến cục bộ và không trả về các tham chiếu đến Collection
hoặc các vòng lặp của nó từ các phương thức. Sau đó, dễ dàng hơn nhiều để kiểm tra tất cả những nơi Collection
có thể được sửa đổi. Nếu Collection
được sử dụng bởi nhiều luồng, thì điều đó là thực tế để đảm bảo rằng các luồng chỉ truy cập với sự đồng bộ Collection
hóa và khóa phù hợp.