เหตุใด ConcurrentModificationException จึงถูกโยนทิ้งและวิธีการดีบัก


130

ฉันกำลังใช้ a Collection( HashMapใช้ทางอ้อมโดย JPA มันก็เกิดขึ้น) แต่ดูเหมือนจะสุ่มรหัสพ่น a ConcurrentModificationException. สาเหตุเกิดจากอะไรและจะแก้ไขปัญหานี้ได้อย่างไร โดยใช้การซิงโครไนซ์บางที?

นี่คือการติดตามสแต็กแบบเต็ม:

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
คุณสามารถระบุบริบทเพิ่มเติมได้หรือไม่? คุณกำลังรวมอัปเดตหรือลบเอนทิตีหรือไม่ เอนทิตีนี้มีความเชื่อมโยงอะไรบ้าง การตั้งค่าแบบเรียงซ้อนของคุณเป็นอย่างไร?
ordnungswidrig

1
จากการติดตามสแต็กคุณจะเห็นว่า Exception เกิดขึ้นขณะที่ทำซ้ำผ่าน HashMap แน่นอนว่าเธรดอื่น ๆ กำลังแก้ไขแผนที่ แต่มีข้อยกเว้นเกิดขึ้นในเธรดที่ทำซ้ำ
Chochos

คำตอบ:


263

นี่ไม่ใช่ปัญหาการซิงโครไนซ์ สิ่งนี้จะเกิดขึ้นหากคอลเลกชันที่อยู่ภายใต้การทำซ้ำถูกแก้ไขโดยสิ่งอื่นที่ไม่ใช่ Iterator เอง

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

สิ่งนี้จะส่งผลConcurrentModificationExceptionเมื่อit.hasNext()มีการเรียกครั้งที่สอง

แนวทางที่ถูกต้องจะเป็น

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

สมมติว่าตัววนซ้ำนี้สนับสนุนการremove()ดำเนินการ


1
เป็นไปได้ แต่ดูเหมือนว่าไฮเบอร์เนตกำลังทำซ้ำซึ่งควรนำไปใช้อย่างถูกต้องตามสมควร อาจมีการโทรกลับเพื่อแก้ไขแผนที่ แต่ไม่น่าเป็นไปได้ ความไม่สามารถคาดเดาได้ชี้ให้เห็นถึงปัญหาการเกิดพร้อมกันที่แท้จริง
Tom Hawtin - แทคไลน์

ข้อยกเว้นนี้ไม่มีส่วนเกี่ยวข้องกับการทำงานพร้อมกันของเธรดซึ่งเกิดจากที่เก็บสำรองของตัววนซ้ำที่ถูกแก้ไข ไม่ว่าจะโดยเธรดอื่นที่ไม่ใช่ไม่สำคัญกับตัววนซ้ำ IMHO เป็นข้อยกเว้นที่มีชื่อไม่ดีเนื่องจากให้การแสดงผลที่ไม่ถูกต้องเกี่ยวกับสาเหตุ
Robin

อย่างไรก็ตามฉันยอมรับว่าหากไม่สามารถคาดเดาได้มีโอกาสมากที่สุดที่จะเกิดปัญหาเธรดซึ่งทำให้เงื่อนไขสำหรับข้อยกเว้นนี้เกิดขึ้น ซึ่งทำให้สับสนมากขึ้นเนื่องจากชื่อข้อยกเว้น
Robin

นี่ถูกต้องและเป็นคำอธิบายที่ดีกว่าคำตอบที่ยอมรับ แต่คำตอบที่ได้รับการยอมรับคือการแก้ไขที่ดี ConcurrentHashMap ไม่อยู่ภายใต้ CME แม้ว่าจะอยู่ในตัววนซ้ำ (แม้ว่าตัววนซ้ำยังคงได้รับการออกแบบมาสำหรับการเข้าถึงเธรดเดียว)
G__

โซลูชันนี้ไม่มีความหมายเนื่องจาก Maps ไม่มีเมธอด iterator () ตัวอย่างของโรบินสามารถใช้ได้กับเช่น Lists
เตอร์

72

ลองใช้ConcurrentHashMapแทนธรรมดาHashMap


นั่นช่วยแก้ปัญหาได้จริงหรือ? ฉันกำลังประสบปัญหาเดียวกัน แต่ฉันสามารถแยกแยะปัญหาเธรดได้อย่างแน่นอน
tobiasbayer

5
อีกวิธีหนึ่งคือสร้างสำเนาของแผนที่และทำซ้ำผ่านสำเนานั้นแทน หรือคัดลอกชุดของคีย์แล้วทำซ้ำเพื่อรับค่าสำหรับแต่ละคีย์จากแผนที่ต้นฉบับ
Chochos

ไฮเบอร์เนตเป็นผู้ที่ทำซ้ำผ่านคอลเลคชันดังนั้นคุณจึงไม่สามารถคัดลอกได้
tobiasbayer

1
ผู้ช่วยชีวิตทันที ไปดูว่าทำไมถึงได้ผลดีดังนั้นฉันจึงไม่ต้องประหลาดใจอีกต่อไป
Valchris

1
ฉันเดาว่ามันไม่ใช่ปัญหาการซิงโครไนซ์มันเป็นปัญหาถ้าการแก้ไขการแก้ไขเดียวกันในขณะที่วนซ้ำวัตถุเดียวกัน
Rais Alam

17

การปรับเปลี่ยนของCollectionในขณะที่ iterating ผ่านที่Collectionใช้Iteratorจะไม่ได้รับอนุญาตโดยส่วนใหญ่ของCollectionชั้นเรียน ไลบรารี Java เรียกความพยายามที่จะแก้ไขไฟล์Collectionในขณะที่ทำซ้ำผ่านมันว่า "การแก้ไขพร้อมกัน" น่าเสียดายที่ชี้ให้เห็นว่าสาเหตุที่เป็นไปได้เพียงอย่างเดียวคือการแก้ไขพร้อมกันหลายเธรด แต่ไม่เป็นเช่นนั้น การใช้เธรดเดียวเป็นไปได้ที่จะสร้างตัววนซ้ำสำหรับCollection(การใช้Collection.iterator()หรือการวนซ้ำขั้นสูงfor ) เริ่มการวนซ้ำ (โดยใช้Iterator.next()หรือเข้าสู่เนื้อหาของforลูปที่ปรับปรุงแล้ว) แก้ไขCollectionจากนั้นทำซ้ำต่อไป

เพื่อช่วยโปรแกรมเมอร์การนำไปใช้งานบางอย่างของCollectionคลาสเหล่านั้นพยายามตรวจจับการปรับเปลี่ยนพร้อมกันที่ผิดพลาดและทำให้เกิดข้อผิดพลาดConcurrentModificationExceptionหากตรวจพบ อย่างไรก็ตามโดยทั่วไปไม่สามารถทำได้และเป็นประโยชน์ในการรับประกันการตรวจจับการแก้ไขพร้อมกันทั้งหมด การใช้งานที่ผิดพลาดจึงCollectionไม่ส่งผลให้เกิดการโยนทิ้งConcurrentModificationExceptionเสมอไป

เอกสารConcurrentModificationExceptionระบุว่า:

ข้อยกเว้นนี้อาจเกิดขึ้นโดยวิธีการที่ตรวจพบการปรับเปลี่ยนวัตถุในเวลาเดียวกันเมื่อไม่อนุญาตให้มีการปรับเปลี่ยนดังกล่าว ...

โปรดทราบว่าข้อยกเว้นนี้ไม่ได้ระบุเสมอว่าอ็อบเจ็กต์ถูกแก้ไขพร้อมกันโดยเธรดอื่น หากเธรดเดียวออกลำดับของการเรียกใช้เมธอดที่ละเมิดสัญญาของอ็อบเจ็กต์อ็อบเจ็กต์อาจโยนข้อยกเว้นนี้ ...

โปรดทราบว่าไม่สามารถรับประกันพฤติกรรมที่ล้มเหลวได้อย่างที่เป็นอยู่โดยทั่วไปแล้วเป็นไปไม่ได้ที่จะทำการรับประกันอย่างหนักหน่วงใด ๆ เมื่อมีการปรับเปลี่ยนพร้อมกันที่ไม่ซิงโครไนซ์ การดำเนินการที่ล้มเหลวอย่างรวดเร็วจะConcurrentModificationExceptionใช้ความพยายามอย่างเต็มที่

โปรดทราบว่า

เอกสารประกอบของHashSet, HashMap, TreeSetและArrayListชั้นเรียนบอกว่านี่:

ตัวทำซ้ำที่ส่งคืน [โดยตรงหรือโดยอ้อมจากคลาสนี้] จะล้มเหลวอย่างรวดเร็ว: ถ้า [คอลเลกชัน] ถูกแก้ไขเมื่อใดก็ได้หลังจากสร้างตัววนซ้ำไม่ว่าจะด้วยวิธีใดก็ตามยกเว้นผ่านวิธีการลบของตัววนซ้ำผู้ทำจะIteratorพ่น a ConcurrentModificationException. ดังนั้นเมื่อเผชิญกับการปรับเปลี่ยนพร้อมกันตัววนซ้ำจะล้มเหลวอย่างรวดเร็วและหมดจดแทนที่จะเสี่ยงต่อพฤติกรรมที่ไม่ได้กำหนดโดยพลการในเวลาที่ไม่กำหนดในอนาคต

โปรดทราบว่าไม่สามารถรับประกันลักษณะการทำงานที่ล้มเหลวอย่างรวดเร็วของตัววนซ้ำได้ตามที่เป็นอยู่โดยทั่วไปแล้วจะไม่สามารถรับประกันได้ยากใด ๆ เมื่อมีการแก้ไขพร้อมกันที่ไม่ซิงโครไนซ์ ตัวทำซ้ำที่รวดเร็วล้มเหลวจะConcurrentModificationExceptionใช้ความพยายามอย่างเต็มที่ ดังนั้นจึงเป็นเรื่องผิดที่จะเขียนโปรแกรมที่ขึ้นอยู่กับข้อยกเว้นนี้เพื่อความถูกต้อง: ควรใช้ลักษณะการทำงานที่ล้มเหลวอย่างรวดเร็วของตัวทำซ้ำเพื่อตรวจจับข้อบกพร่องเท่านั้น

โปรดทราบอีกครั้งว่าพฤติกรรม "ไม่สามารถรับประกันได้" และเป็นเพียง "ตามความพยายามอย่างเต็มที่"

เอกสารของวิธีการต่างๆของMapอินเทอร์เฟซระบุว่า:

การใช้งานที่ไม่เกิดขึ้นพร้อมกันควรแทนที่วิธีการนี้และConcurrentModificationExceptionหากตรวจพบว่าฟังก์ชันการทำแผนที่แก้ไขแผนที่นี้ในระหว่างการคำนวณโดยใช้ความพยายามอย่างเต็มที่ การใช้งานพร้อมกันควรแทนที่วิธีการนี้และIllegalStateExceptionหากตรวจพบว่าฟังก์ชันการทำแผนที่จะแก้ไขแผนที่นี้ในระหว่างการคำนวณและการคำนวณผลลัพธ์จะไม่เสร็จสมบูรณ์

โปรดทราบอีกครั้งว่าจำเป็นต้องมี "พื้นฐานความพยายามอย่างดีที่สุด" เท่านั้นสำหรับการตรวจจับและConcurrentModificationExceptionแนะนำอย่างชัดเจนสำหรับคลาสที่ไม่พร้อมกันเท่านั้น (ไม่ใช่เธรด - ปลอดภัย)

แก้จุดบกพร่อง ConcurrentModificationException

ดังนั้นเมื่อคุณเห็นสแต็กแทร็กเนื่องจาก a ConcurrentModificationExceptionคุณไม่สามารถสันนิษฐานได้ทันทีว่าสาเหตุคือการเข้าถึงแบบมัลติเธรดที่ไม่ปลอดภัยไปยังไฟล์Collection. คุณต้องตรวจสอบการติดตามสแต็กเพื่อพิจารณาว่าคลาสของการCollectionโยนข้อยกเว้น (เมธอดของคลาสจะโยนมันโดยตรงหรือโดยอ้อม) และสำหรับCollectionอ็อบเจ็กต์ใด จากนั้นคุณต้องตรวจสอบว่าสามารถแก้ไขวัตถุนั้นได้จากที่ใด

  • สาเหตุที่พบบ่อยที่สุดคือการแก้ไขCollectionภายในforลูปที่ปรับปรุงแล้วบนCollection. เพียงเพราะคุณไม่เห็นIteratorวัตถุในซอร์สโค้ดของคุณไม่ได้หมายความว่าไม่มีIterator! โชคดีที่หนึ่งในข้อความของforลูปที่ผิดปกติมักจะอยู่ในสแต็กแทร็กดังนั้นการติดตามข้อผิดพลาดจึงมักทำได้ง่าย
  • กรณีที่ยากกว่าคือเมื่อรหัสของคุณส่งผ่านการอ้างอิงไปยังCollectionวัตถุ โปรดทราบว่ามุมมองที่ไม่สามารถแก้ไขได้ของคอลเลกชัน (เช่นผลิตโดยCollections.unmodifiableList()) ยังคงมีการอ้างอิงถึงคอลเล็กชันที่ปรับเปลี่ยนได้ดังนั้นการทำซ้ำในคอลเล็กชันที่ "ไม่สามารถแก้ไขได้" อาจทำให้เกิดข้อยกเว้นได้ (การแก้ไขได้ดำเนินการที่อื่นแล้ว) อื่น ๆมุมมองของคุณCollectionเช่นรายการย่อย , MapชุดรายการและMapชุดที่สำคัญยังรักษาอ้างอิงไปที่เดิม Collection(แก้ไขได้) ซึ่งอาจเป็นปัญหาได้แม้สำหรับเธรดที่ปลอดภัยCollectionเช่นCopyOnWriteList; อย่าถือว่าคอลเลกชันที่ปลอดภัยต่อเธรด (พร้อมกัน) ไม่สามารถส่งข้อยกเว้นได้
  • การดำเนินการใดที่สามารถแก้ไขข้อผิดพลาดCollectionได้ในบางกรณี ยกตัวอย่างเช่นการปรับเปลี่ยนคอลเลกชันLinkedHashMap.get()
  • กรณีที่ยากที่สุดเมื่อข้อยกเว้นคือเนื่องจากการปรับเปลี่ยนพร้อมกันโดยหลายหัวข้อ

การเขียนโปรแกรมเพื่อป้องกันข้อผิดพลาดในการแก้ไขพร้อมกัน

หากเป็นไปได้ให้ จำกัด การอ้างอิงทั้งหมดไปยังCollectionอ็อบเจ็กต์เพื่อป้องกันการแก้ไขพร้อมกันได้ง่ายขึ้น ทำให้วัตถุหรือตัวแปรท้องถิ่นและไม่กลับอ้างอิงถึงหรือ iterators จากวิธีการ จากนั้นจะง่ายกว่ามากในการตรวจสอบสถานที่ทั้งหมดที่สามารถแก้ไขได้ หากจะใช้กับเธรดหลายเธรดจะเป็นการปฏิบัติเพื่อให้แน่ใจว่าเธรดเข้าถึงเฉพาะด้วยการซิงโครไนซ์และการล็อกที่เหมาะสมCollectionprivateCollectionCollectionCollectionCollection


ฉันสงสัยว่าเหตุใดจึงไม่อนุญาตให้แก้ไขพร้อมกันในกรณีที่มีเธรดเดียว จะเกิดปัญหาอะไรได้หากเธรดเดียวได้รับอนุญาตให้ทำการแก้ไขพร้อมกันบนแผนที่แฮชปกติ
MasterJoe


2

ดูเหมือนปัญหาการซิงโครไนซ์ Java น้อยลงและอื่น ๆ เช่นปัญหาการล็อกฐานข้อมูล

ฉันไม่รู้ว่าการเพิ่มเวอร์ชันลงในคลาสถาวรทั้งหมดของคุณจะแยกออกหรือไม่ แต่นั่นเป็นวิธีหนึ่งที่ Hibernate สามารถให้สิทธิ์การเข้าถึงเฉพาะแถวในตาราง

อาจเป็นไปได้ว่าระดับการแยกจะต้องสูงขึ้น หากคุณอนุญาตให้ "อ่านสกปรก" บางทีคุณอาจต้องเพิ่มข้อมูลให้เป็นอนุกรม


ฉันคิดว่าพวกเขาหมายถึง Hashtable จัดส่งเป็นส่วนหนึ่งของ JDK 1.0 เช่นเดียวกับ Vector มันถูกเขียนให้เธรดปลอดภัยและช้า ทั้งสองถูกแทนที่ด้วยทางเลือกที่ปลอดภัยที่ไม่ใช่เธรด: HashMap และ ArrayList จ่ายสำหรับสิ่งที่คุณใช้
duffymo

0

ลอง CopyOnWriteArrayList หรือ CopyOnWriteArraySet ขึ้นอยู่กับสิ่งที่คุณกำลังพยายามทำ


0

โปรดทราบว่าคำตอบที่เลือกไม่สามารถนำไปใช้กับบริบทของคุณได้โดยตรงก่อนการแก้ไขบางอย่างหากคุณพยายามลบบางรายการออกจากแผนที่ในขณะที่ทำแผนที่ซ้ำเช่นเดียวกับฉัน

ฉันแค่ยกตัวอย่างการทำงานของฉันที่นี่สำหรับมือใหม่เพื่อประหยัดเวลา:

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

ฉันพบข้อยกเว้นนี้เมื่อพยายามลบ x รายการสุดท้ายออกจากรายการ myList.subList(lastIndex, myList.size()).clear();เป็นทางออกเดียวที่ใช้ได้ผลสำหรับฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.