Sửa đổi một Collectionkhi iterating qua đó Collectionbằng cách sử dụng một Iteratorlà không được phép bởi hầu hết các Collectionlớp học. Thư viện Java gọi một nỗ lực để sửa đổi Collectiontrong 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 forvò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 Collectionlớp đó cố gắng phát hiện sửa đổi đồng thời sai lầm và ném ConcurrentModificationExceptionnế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 Collectionkhông phải lúc nào cũng dẫn đến ném ConcurrentModificationException.
Các tài liệu ConcurrentModificationExceptionnó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, TreeSetvà ArrayListcá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ó Iteratorsẽ 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 ConcurrentModificationExceptiontrê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 Mapgiao 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 ConcurrentModificationExceptionnế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 IllegalStateExceptionnế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
Collectiontrong forvò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 Iteratorcó ở đó! May mắn thay, một trong những tuyên bố của forvò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ụ , Mapbộ mục nhập và Mapbộ 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
Collectioncó 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 Collectionmộ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 Collectionhoặ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 Collectioncó 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ộ Collectionhóa và khóa phù hợp.