क्यों एक समवर्तीमॉडिफिकेशन अपवाद को फेंक दिया गया है और इसे कैसे डीबग करना है


130

मैं एक Collection( HashMapअप्रत्यक्ष रूप से जेपीए द्वारा उपयोग किया जाता है, यह ऐसा होता है) का उपयोग कर रहा हूं , लेकिन जाहिर तौर पर कोड बेतरतीब ढंग से फेंकता है 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
स्टैक ट्रेस से आप देख सकते हैं कि हैशमैप के माध्यम से पुनरावृति करते समय अपवाद होता है। निश्चित रूप से कुछ अन्य धागा मानचित्र को संशोधित कर रहा है, लेकिन अपवाद उस थ्रेड में होता है जो इसे पुनरावृत्त कर रहा है।
चोचोस 31'10

जवाबों:


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
संभवतः, लेकिन ऐसा लगता है मानो हाइबरनेट पुनरावृति कर रहा है, जिसे यथोचित रूप से सही ढंग से लागू किया जाना चाहिए। मानचित्र को संशोधित करने के लिए कॉलबैक हो सकता है, लेकिन यह संभावना नहीं है। अनपेक्षितता एक वास्तविक संगामिति समस्या की ओर इशारा करती है।
टॉम हॉल्टिन -

इस अपवाद का सूत्रण संगति से कोई लेना-देना नहीं है, यह पुनरावृत्ति करने वाले पुनरावृत्ति स्टोर के कारण होता है। क्या पुनरावृत्ति करने के लिए कोई फर्क नहीं पड़ता के एक और धागे से। IMHO यह एक खराब नामित अपवाद है क्योंकि यह कारण का गलत प्रभाव देता है।
रॉबिन

मैं इस बात से सहमत हूं कि अगर यह अप्रत्याशित है, तो सबसे अधिक संभावना एक थ्रेडिंग मुद्दा है जो इस अपवाद के लिए शर्तों को उत्पन्न कर रहा है। जो अपवाद नाम के कारण इसे और अधिक भ्रमित करता है।
रॉबिन

यह सही है और स्वीकृत उत्तर की तुलना में बेहतर व्याख्या है, लेकिन स्वीकृत उत्तर एक अच्छा समाधान है। समवर्ती हाशिए सीएमई के अधीन नहीं है, यहां तक ​​कि एक इटरेटर के अंदर भी (हालांकि यह इटरेटर अभी भी सिंगल-थ्रेड एक्सेस के लिए डिज़ाइन किया गया है)।
G__

इस समाधान का कोई मतलब नहीं है, क्योंकि नक्शे में पुनरावृत्ति () विधि नहीं है। रॉबिन का उदाहरण उदाहरणों की सूचियों पर लागू होगा।
पीटर

72

ConcurrentHashMapएक सादे के बजाय का उपयोग करने का प्रयास करेंHashMap


क्या इससे वास्तव में समस्या हल हुई? मैं एक ही मुद्दे का सामना कर रहा हूं, लेकिन मैं निश्चित रूप से किसी भी थ्रेडिंग मुद्दों को खारिज कर सकता हूं।
Tobiasbayer

5
एक अन्य उपाय यह है कि नक्शे की एक प्रति बनाएं और उस प्रति के माध्यम से पुनरावृति करें। या कुंजी के सेट को कॉपी करें और उनके माध्यम से पुनरावृति करें, मूल मानचित्र से प्रत्येक कुंजी के लिए मूल्य प्राप्त करना।
चोकोस

यह हाइबरनेट है जो संग्रह के माध्यम से पुनरावृत्ति कर रहा है ताकि आप इसे कॉपी न कर सकें।
टोबैसबेयर

1
तत्काल बचाने वाला। यह देखने के लिए कि यह इतनी अच्छी तरह से क्यों काम करता है ताकि मुझे सड़क पर और अधिक आश्चर्य न हो।
वलक्रिस

1
मुझे लगता है कि इसकी सिंक्रनाइज़ेशन समस्या यह समस्या नहीं है अगर एक ही वस्तु को संशोधित करते समय एक ही संशोधन किया जाए।
रईस आलम

17

एक का संशोधन Collectionकरते हुए बार-बार दोहराना के माध्यम से उस Collectionका उपयोग कर एक Iteratorहै की अनुमति नहीं के सबसे द्वारा Collectionकक्षाएं। जावा लाइब्रेरी Collectionइसे "समवर्ती संशोधन" के माध्यम से थोड़ी देर में संशोधित करने का प्रयास कहता है । यह दुर्भाग्य से एकमात्र संभव कारण बताता है कि एक साथ कई थ्रेड्स द्वारा संशोधन है, लेकिन ऐसा नहीं है। केवल एक धागे Collectionका उपयोग करके (उपयोग Collection.iterator()या बढ़ाया forलूप ) के लिए एक पुनरावृत्ति बनाना संभव है , पुनरावृत्ति शुरू करें (उपयोग Iterator.next(), या समकक्ष रूप से बढ़ाया forलूप के शरीर में प्रवेश ), संशोधित करेंCollection , फिर पुनरावृत्ति जारी रखें।

प्रोग्रामर की मदद करने के लिए, उन वर्गों के कुछ कार्यान्वयन गलत समवर्ती संशोधन का पता लगाने का प्रयास करते हैं, और यदि वे इसका पता लगाते हैं तो उन्हें फेंक देते हैं । हालांकि, सभी समवर्ती संशोधनों का पता लगाने की गारंटी देना सामान्य और संभव नहीं है। इसलिए गलत का गलत इस्तेमाल हमेशा फेक नहीं होता ।CollectionConcurrentModificationExceptionCollectionConcurrentModificationException

का प्रलेखन ConcurrentModificationExceptionकहता है:

यह अपवाद उन विधियों द्वारा फेंका जा सकता है, जब किसी वस्तु के समवर्ती संशोधन का पता चला है, जब ऐसा संशोधन अनुमेय नहीं है ...

ध्यान दें कि यह अपवाद हमेशा इंगित नहीं करता है कि किसी वस्तु को एक अलग धागे द्वारा समवर्ती रूप से संशोधित किया गया है। यदि कोई एकल थ्रेड किसी विधि के अनुबंध का उल्लंघन करने वाली विधि इनवोकेशन का अनुक्रम जारी करता है, तो ऑब्जेक्ट इस अपवाद को फेंक सकता है ...

ध्यान दें कि असफल-तेज़ व्यवहार की गारंटी नहीं दी जा सकती क्योंकि यह आम तौर पर बोल रहा है, असम्बद्ध समवर्ती संशोधन की उपस्थिति में किसी भी हार्ड गारंटी को असंभव बनाना। असफल-फास्ट संचालन ConcurrentModificationExceptionएक सर्वोत्तम प्रयास के आधार पर फेंकते हैं।

ध्यान दें कि

के प्रलेखन HashSet, HashMap, TreeSetऔर ArrayListकक्षाओं इस कहते हैं:

पुनरावृत्तियाँ [इस श्रेणी से प्रत्यक्ष या अप्रत्यक्ष रूप से] विफल हो जाती हैं - यदि संग्रहकर्ता बनने के बाद किसी भी समय [संग्रह] संशोधित किया जाता है, तो किसी भी तरह से पुनरावृत्ति करने वाले के स्वयं के निष्कासन विधि के अलावा, Iteratorफेंकता है ConcurrentModificationException। इस प्रकार, समवर्ती संशोधन के सामने, भविष्य में एक अनिर्धारित समय पर मनमाना, गैर-निर्धारक व्यवहार को जोखिम में डालने के बजाय, यह जल्दी और साफ-सुथरा हो जाता है।

ध्यान दें कि इट्रेटर के असफल-तेज़ व्यवहार की गारंटी नहीं दी जा सकती है क्योंकि यह आमतौर पर बोल रहा है, असम्बद्ध समवर्ती संशोधन की उपस्थिति में किसी भी हार्ड गारंटी को असंभव बनाता है। असफल-तेज़ पुनरावृत्तियों ConcurrentModificationExceptionको सर्वोत्तम प्रयास के आधार पर फेंकते हैं। इसलिए, एक प्रोग्राम लिखना गलत होगा जो इसके सही होने के लिए इस अपवाद पर निर्भर करता है: पुनरावृत्तियों के असफल-तेज़ व्यवहार का उपयोग केवल बग का पता लगाने के लिए किया जाना चाहिए

ध्यान दें कि व्यवहार "गारंटी नहीं दे सकता है" और केवल "सर्वश्रेष्ठ प्रयास के आधार पर" है।

Mapइंटरफ़ेस के कई तरीकों का प्रलेखन यह कहता है:

गैर-समवर्ती कार्यान्वयन को इस पद्धति को ओवरराइड करना चाहिए और सर्वोत्तम-प्रयास के आधार पर, ConcurrentModificationExceptionयदि यह पता चला है कि मैपिंग फ़ंक्शन कम्प्यूटेशन के दौरान इस मैप को संशोधित करता है। समवर्ती कार्यान्वयन इस पद्धति को ओवरराइड करना चाहिए और, सर्वोत्तम-प्रयास के आधार पर, IllegalStateExceptionयदि यह पता लगाया जाए कि मैपिंग फ़ंक्शन कम्प्यूटेशन के दौरान इस मैप को संशोधित करता है और परिणामस्वरूप कम्प्यूटेशन कभी पूरा नहीं होगा।

फिर से ध्यान दें कि पता लगाने के लिए केवल "सर्वश्रेष्ठ-प्रयास का आधार" आवश्यक है, और ConcurrentModificationExceptionस्पष्ट रूप से केवल गैर-समवर्ती (गैर-थ्रेड-सुरक्षित) वर्गों के लिए सुझाव दिया गया है।

डिबगिंग ConcurrentModificationException

इसलिए, जब आप एक के कारण एक स्टैक-ट्रेस देखते हैं, तो आप ConcurrentModificationExceptionतुरंत यह नहीं मान सकते कि इसका कारण असुरक्षित मल्टी-थ्रेडेड एक्सेस टू है Collection। आपको यह निर्धारित करने के लिए स्टैक-ट्रेस की जांच करनी चाहिए कि किस वर्ग Collectionने अपवाद को फेंक दिया ( कक्षा का एक तरीका प्रत्यक्ष या अप्रत्यक्ष रूप से इसे फेंक दिया जाएगा), और किस Collectionवस्तु के लिए। फिर आपको यह जांचना होगा कि उस वस्तु को कहां से संशोधित किया जा सकता है।

  • सबसे आम कारण में Collectionएक बढ़ाया forलूप के भीतर संशोधन है Collection। सिर्फ इसलिए कि आप Iteratorअपने स्रोत कोड में एक वस्तु नहीं देखते हैं इसका मतलब यह नहीं है कि वहाँ कोई नहीं Iteratorहै! सौभाग्य से, दोषपूर्ण forलूप के बयानों में से एक आमतौर पर स्टैक-ट्रेस में होगा, इसलिए त्रुटि को ट्रैक करना आमतौर पर आसान होता है।
  • एक पेचीदा मामला तब होता है जब आपका कोड Collectionऑब्जेक्ट के संदर्भ में चारों ओर से गुजरता है । ध्यान दें कि संग्रह के अनम्य विचार (जैसे कि द्वारा निर्मित Collections.unmodifiableList()) संशोधित संग्रह के संदर्भ को बनाए रखते हैं, इसलिए "अनमॉडिफ़ेबल" संग्रह पर पुनरावृत्ति अपवाद को फेंक सकती है (संशोधन कहीं और किया गया है)। आपके अन्य विचारCollection , जैसे उप सूचियाँ , Mapप्रवेश सेट और Mapमुख्य सेट भी मूल (परिवर्तनीय) के संदर्भ को बनाए रखते हैं Collection। यह थ्रेड-सेफ के लिए भी एक समस्या हो सकती है Collection, जैसे CopyOnWriteList; यह मत मानो कि थ्रेड-सुरक्षित (समवर्ती) संग्रह कभी अपवाद नहीं फेंक सकता है।
  • कौन से ऑपरेशन संशोधित कर Collectionसकते हैं कुछ मामलों में अप्रत्याशित हो सकता है। उदाहरण के लिए, LinkedHashMap.get()इसके संग्रह को संशोधित करता है
  • सबसे कठिन मामलों रहे हैं जब अपवाद है एक से अधिक थ्रेड द्वारा समवर्ती संशोधन के कारण।

समवर्ती संशोधन त्रुटियों को रोकने के लिए प्रोग्रामिंग

जब संभव हो, किसी Collectionऑब्जेक्ट के सभी संदर्भों को सीमित करें , इसलिए समवर्ती संशोधनों को रोकना आसान है। Collectionएक privateऑब्जेक्ट या एक स्थानीय चर बनाएं , और Collectionविधियों से या उसके पुनरावृत्तियों के संदर्भ न लौटें । फिर उन सभी स्थानों की जांच करना बहुत आसान है जहां Collectionसंशोधित किया जा सकता है। यदि Collectionकई थ्रेड्स का उपयोग किया जाना है, तो यह सुनिश्चित करना व्यावहारिक है कि थ्रेड्स Collectionउपयुक्त सिंक्रोनाइजेशन और लॉकिंग के साथ ही एक्सेस करते हैं ।


मुझे आश्चर्य है कि एक सूत्र के मामले में समवर्ती संशोधन की अनुमति क्यों नहीं है। यदि एक एकल धागे को नियमित हैश मानचित्र पर एक समवर्ती संशोधन करने की अनुमति मिलती है तो क्या समस्याएं हो सकती हैं?
मास्टरजॉय

4

जावा 8 में, आप लैम्ब्डा अभिव्यक्ति का उपयोग कर सकते हैं:

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

2

यह जावा तुल्यकालन समस्या की तरह कम और डेटाबेस लॉकिंग समस्या की तरह अधिक लगता है।

मुझे नहीं पता कि आपके सभी लगातार वर्गों के लिए एक संस्करण जोड़ने से यह छंट जाएगा, लेकिन यह एक तरीका है कि हाइबरनेट एक तालिका में पंक्तियों के लिए विशेष पहुंच प्रदान कर सकता है।

हो सकता है कि अलगाव का स्तर अधिक होना चाहिए। यदि आप "गंदे रीड्स" की अनुमति देते हैं, तो हो सकता है कि आपको सीरियल करने योग्य बनाने की आवश्यकता हो।


मुझे लगता है कि उनका मतलब हैशटेबल था। इसने JDK 1.0 के भाग के रूप में भेज दिया। वेक्टर की तरह, यह थ्रेड सुरक्षित होने के लिए लिखा गया था - और धीमा। दोनों को गैर-थ्रेड सुरक्षित विकल्पों द्वारा अधिगृहीत किया गया है: हैशपेयर और एरियरिस्ट। जो आप उपयोग करते हैं उसके लिए भुगतान करें।
डफिमो

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();मेरे लिए काम करने वाला एकमात्र समाधान था।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.