Tại sao một concExModificationException bị ném và làm thế nào để gỡ lỗi nó


130

Tôi đang sử dụng một Collection( HashMapđược sử dụng gián tiếp bởi JPA, điều đó xảy ra), nhưng rõ ràng ngẫu nhiên mã ném a ConcurrentModificationException. Điều gì gây ra nó và làm thế nào để tôi khắc phục vấn đề này? Bằng cách sử dụng một số đồng bộ hóa, có lẽ?

Dưới đây là toàn bộ dấu vết ngăn xếp:

Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
        at java.util.HashMap$ValueIterator.next(Unknown Source)
        at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
        at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
        at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:130)

1
Bạn có thể cung cấp thêm một số bối cảnh? Bạn đang hợp nhất, cập nhật hoặc xóa một thực thể? Những hiệp hội nào làm thực thể này có? Điều gì về cài đặt xếp tầng của bạn?
ordnungswidrig

1
Từ dấu vết ngăn xếp, bạn có thể thấy Ngoại lệ xảy ra trong khi lặp qua HashMap. Chắc chắn một số luồng khác đang sửa đổi bản đồ nhưng ngoại lệ xảy ra trong luồng đang lặp lại.
Chochos

Câu trả lời:


263

Đây không phải là một vấn đề đồng bộ hóa. Điều này sẽ xảy ra nếu bộ sưu tập cơ bản đang được lặp đi lặp lại được sửa đổi bởi bất cứ thứ gì khác ngoài chính Bộ lặp.

Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
   Entry item = it.next();
   map.remove(item.getKey());
}

Điều này sẽ ném một ConcurrentModificationExceptionkhi it.hasNext()được gọi là lần thứ hai.

Cách tiếp cận đúng sẽ là

   Iterator it = map.entrySet().iterator();
   while (it.hasNext())
   {
      Entry item = it.next();
      it.remove();
   }

Giả sử iterator này hỗ trợ remove()hoạt động.


1
Có thể, nhưng có vẻ như Hibernate đang thực hiện việc lặp lại, cần được thực hiện một cách hợp lý chính xác. Có thể có cuộc gọi lại sửa đổi bản đồ, nhưng điều đó là không thể. Sự khó lường chỉ ra một vấn đề tương tranh thực tế.
Tom Hawtin - tackline

Ngoại lệ này không liên quan gì đến xử lý đồng thời luồng, nó được gây ra bởi kho lưu trữ sao lưu của trình lặp được sửa đổi. Cho dù bởi một luồng khác không quan trọng đối với iterator. IMHO nó là một ngoại lệ được đặt tên kém vì nó cho ấn tượng không chính xác về nguyên nhân.
Robin

Tuy nhiên, tôi đồng ý rằng nếu không thể đoán trước được, rất có thể có sự cố luồng đang gây ra các điều kiện cho ngoại lệ này xảy ra. Điều này làm cho tất cả khó hiểu hơn vì tên ngoại lệ.
Robin

Điều này là chính xác và một lời giải thích tốt hơn câu trả lời được chấp nhận, nhưng câu trả lời được chấp nhận là một sửa chữa tốt đẹp. ConcảnHashMap không chịu sự điều chỉnh của CME, ngay cả bên trong một trình vòng lặp (mặc dù trình vòng lặp vẫn được thiết kế để truy cập chuỗi đơn).
G__

Giải pháp này không có điểm nào, vì Maps không có phương thức iterator (). Ví dụ của Robin sẽ được áp dụng cho các Danh sách.
peter

72

Hãy thử sử dụng ConcurrentHashMapthay vì đơn giảnHashMap


Điều đó đã thực sự giải quyết vấn đề? Tôi đang gặp vấn đề tương tự nhưng tôi chắc chắn có thể loại trừ bất kỳ vấn đề luồng nào.
tobiasbayer

5
Một giải pháp khác là tạo một bản sao của bản đồ và lặp lại thông qua bản sao đó. Hoặc sao chép bộ khóa và lặp qua chúng, nhận giá trị cho mỗi khóa từ bản đồ gốc.
Chochos

Chính Hibernate đang lặp lại qua bộ sưu tập nên bạn không thể sao chép nó.
tobiasbayer

1
Cứu tinh tức thì. Xem xét lý do tại sao điều này làm việc rất tốt để tôi không gặp nhiều bất ngờ hơn nữa.
Valchris

1
Tôi đoán vấn đề không đồng bộ hóa của nó là vấn đề nếu sửa đổi cùng một sửa đổi trong khi lặp cùng một đối tượng.
Rais Alam

17

Sửa đổi một Collectionkhi iterating qua đó Collectionbằng cách sử dụng một Iteratorkhô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, TreeSetArrayListcá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ậpMapbộ 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ệ 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.


Tôi tự hỏi tại sao sửa đổi đồng thời không được phép trong trường hợp của một chủ đề. Vấn đề gì có thể xảy ra nếu một luồng duy nhất được phép thực hiện sửa đổi đồng thời trên bản đồ băm thông thường?
MasterJoe

4

Trong Java 8, bạn có thể sử dụng biểu thức lambda:

map.keySet().removeIf(key -> key condition);

2

Nghe có vẻ giống như một vấn đề đồng bộ hóa Java và giống như một vấn đề khóa cơ sở dữ liệu.

Tôi không biết nếu thêm một phiên bản cho tất cả các lớp liên tục của bạn sẽ sắp xếp nó, nhưng đó là một cách mà Hibernate có thể cung cấp quyền truy cập độc quyền vào các hàng trong một bảng.

Có thể là mức cô lập cần phải cao hơn. Nếu bạn cho phép "đọc bẩn", có thể bạn cần phải tăng lên để tuần tự hóa.


Tôi nghĩ họ có nghĩa là Hashtable. Nó được vận chuyển như một phần của JDK 1.0. Giống như Vector, nó được viết là chủ đề an toàn - và chậm. Cả hai đã được thay thế bởi các lựa chọn an toàn không có luồng: HashMap và ArrayList. Trả tiền cho những gì bạn sử dụng.
duffymo

0

Hãy thử CopyOnWriteArrayList hoặc CopyOnWriteArraySet tùy thuộc vào những gì bạn đang cố gắng làm.


0

Lưu ý rằng câu trả lời được chọn không thể được áp dụng trực tiếp vào ngữ cảnh của bạn trước một số sửa đổi, nếu bạn đang cố xóa một số mục khỏi bản đồ trong khi lặp lại bản đồ giống như tôi.

Tôi chỉ đưa ra ví dụ làm việc của tôi ở đây cho người mới để tiết kiệm thời gian của họ:

HashMap<Character,Integer> map=new HashMap();
//adding some entries to the map
...
int threshold;
//initialize the threshold
...
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
    Map.Entry<Character,Integer> item=(Map.Entry<Character,Integer>)it.next();
    //it.remove() will delete the item from the map
    if((Integer)item.getValue()<threshold){
        it.remove();
    }

0

Tôi gặp phải trường hợp ngoại lệ này khi cố gắng xóa x mục cuối cùng khỏi danh sách. myList.subList(lastIndex, myList.size()).clear();là giải pháp duy nhất hiệu quả với tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.