2012-06-14 16 views
5

का उपयोग करते समय एक सूची का समवर्ती संशोधन निम्नलिखित कोड ConcurrentModificationException या अन्य दुष्प्रभावों का कारण बनेंगे?कॉपी कन्स्ट्रक्टर

ArrayList<String> newList = new ArrayList<String>(list); 

यह देखते हुए कि सूची का आकार बहुत बड़ा है और एक अन्य धागा समवर्ती सूची को संशोधित किया जाता है जब कोड ऊपर निष्पादित हो रही है।

उत्तर

8

संपादित करें:

मेरे प्रारंभिक जवाब हाँ है, लेकिन जैसा कि @JohnVint सही ढंग से बताते हैं, यह एक ConcurrentModificationException के बाद से कवर ArrayList सरणी System.arrayCopy(...) का उपयोग कर डुप्लिकेट कर रही है के तहत नहीं किया जाएगा। अंत में कोड स्निपेट देखें।

समस्या यह है कि एक और थ्रेड तत्व सरणी में परिवर्तन कर रहा है क्योंकि आप यह प्रतिलिपि करते हैं। आपको IndexOutOfBoundsException, अनियमित सरणी मान, या यहां तक ​​कि कुछ प्रकार के देशी मेमोरी एक्सेस अपवाद भी हो सकते हैं क्योंकि System.arraycopy(...) देशी कोड में किया जाता है।

आपको इन दौड़ की स्थिति के खिलाफ सुरक्षा के लिए अद्यतन और प्रतिलिपि के दौरान सूची में सिंक्रनाइज़ करने की आवश्यकता होगी और यह सुनिश्चित करने के लिए मेमोरी बाधा स्थापित करें कि ArrayList का बैक तत्व तत्व उचित रूप से अद्यतित है।


public ArrayList(Collection<? extends E> c) { 
    elementData = c.toArray(); 
    ... 
} 

// ArrayList 
public Object[] toArray() { 
     return Arrays.copyOf(elementData, size); 
} 

// Arrays 
public static <T,U> T[] copyOf(U[] original, int newLength, 
    Class<? extends T[]> newType) { 
    ... 
    System.arraycopy(original, 0, copy, 0, 
     Math.min(original.length, newLength)); 
} 

// System 
public static native void arraycopy(Object src, int srcPos, 
    Object dest, int destPos, int length); 
+0

एक 'Collections.synchronizedList' (सूची) इस पर बार-बार दोहराना के साथ एक सीएमई प्राप्त कर सकते हैं जब तक कि यह' सिंक्रनाइज़ किया गया है { } ' –

+0

यिक्स, दाएं @ पीटर, धन्यवाद। मैं जवाब ठीक कर दूंगा। – Gray

+0

मैं सूची आकार 1000000 के साथ व्यावहारिक रूप से सीएमई प्राप्त नहीं कर सका। सरणी सूची के विशेष मामले में जहां बैक किए गए सरणी को System.arraycopy के माध्यम से कॉपी प्रतिलिपि में कॉपी किया गया है, क्या यह वर्तमान सरणी का स्नैपशॉट प्राप्त करेगा? सूची का आकार बदलना प्रभावित करेगा? – Sushant

1

आप यहाँ क्या कर रहे के बारे में सोचने की जरूरत है। यदि list की कक्षा थ्रेड-सुरक्षित नहीं है, तो आप को इस कोड के साथ पूरी तरह से newList --a सीएमई को अपनी समस्याओं का कम से कम नष्ट कर सकते हैं। (मैं एक वर्ग का सुझाव दूंगा जो सीएमई नहीं फेंकता है, लेकिन इस मामले में एक सीएमई अच्छा चीज है।) ध्यान दें: यह कोड परीक्षण करना मुश्किल है। आपको हर विफलता के बीच कहीं शून्य और अरबों समस्या-मुक्त रनों के बीच कहीं मिलेगा, और असफलता बहुत सूक्ष्म हो सकती है, हालांकि वे बड़े पैमाने पर और तर्कसंगत स्पष्टीकरण से अधिक होने की संभावना है।

सबसे तेज़ फिक्स list लॉक करना है। आप इसे हर जगह लॉक करना सुनिश्चित करना चाहते हैं; आप सूची को वास्तव में लॉक नहीं कर रहे हैं, आप उस कोड के ब्लॉक को लॉक कर रहे हैं जिसे आप एक्सेस कर रहे हैं। आपको सभी एक्सेस लॉक करना होगा। नकारात्मकता यह है कि नई सूची बनाई जा रही है, जबकि आप अन्य धागे को अवरुद्ध करेंगे। यह वास्तव में जाने का रास्ता है। हालांकि, यदि आप कहते हैं, "सूची बहुत बड़ी है", तो आप प्रदर्शन के बारे में चिंतित हो सकते हैं, इसलिए मैं आगे बढ़ूंगा ...

newList को अपरिवर्तनीय माना जाता है और यह आप कर सकते हैं एक बार बनाया गया इसका लगातार उपयोग। बहुत सारे कोड अब newList समस्याओं के बिना और असंगतताओं के डर के बिना पढ़ सकते हैं। लेकिन प्रारंभिक सृजन के साथ अभी भी पकड़ है।

अगला चरण list को java.util.ConcurrentLinkedQueue बनाना है। (एक समवर्ती नक्शा है और सेट करें यदि आपको कुछ फैनसीयर चाहिए।) इस चीज़ में धागे का एक गुच्छा हो सकता है जबकि एक गुच्छा अधिक जोड़ दिया जाता है और इसे हटाया जाता है, और यह हमेशा काम करता है। इसमें शामिल नहीं हो सकता है, लेकिन एक इटरेटर एक अनंत लूप में नहीं जायेगा (जैसा कि list एक java.util.LinkedList थे) हो सकता है। इससे newList एक कोर पर बनाया जाता है जबकि आपका अन्य धागा दूसरे पर काम करता है।

डाउनसाइड्स: यदि list एक ऐरेलिस्ट था, तो आपको समवर्ती कक्षा में स्विच करने के लिए थोड़ा सा काम मिल सकता है। समवर्ती कक्षाएं अधिक स्मृति का उपयोग करती हैं और आम तौर पर ऐरेलिस्ट से धीमी होती हैं। अधिक महत्वपूर्ण: list की सामग्री असंगत हो सकती है। (असल में, आपके पास पहले से ही यह समस्या है।) आप अन्य थ्रेड में एक ही समय में ए और बी प्रविष्टियां जोड़ या निकाल सकते हैं, और उम्मीद कर सकते हैं कि या तो दोनों या तो न तो newList में होंगे, वास्तव में यह केवल एक के लिए आसान है वहां रहें, किसी के जोड़े जाने या हटाने के बाद इटेटरेटर आ रहा है लेकिन दूसरे के सामने है। (सिंगल कोर मशीनों में इस समस्या को उतना ही नहीं है।) लेकिन यदि list पहले से ही स्थिर, विकृत प्रवाह में होने के बारे में सोचा गया है, तो यह वही हो सकता है जो आप चाहते हैं।

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

इससे भी बदतर, जब आप प्रविष्टियां जोड़ते हैं, तो वे अपनी पुरानी सरणी मुक्त करते हैं और एक नया, बड़ा, आवंटित करते हैं जिसके परिणामस्वरूप मुक्त स्मृति खंडित होती है। यही है, मुफ्त मेमोरी ज्यादातर पुराने सरणी से निकलती है, जिनमें से कोई भी अगले आवंटन के लिए उपयोग करने के लिए पर्याप्त नहीं है। कचरा कलेक्टर यह सब डिफ्रैगमेंट करने का प्रयास करेगा, लेकिन यह बहुत काम है और जीसी मुक्त ब्लॉकों को पुनर्व्यवस्थित करने के लिए समय निकालने के बजाय मेमोरी अपवादों को फेंकने के इच्छुक है, इसलिए यह आपको सबसे बड़ा-अभी तक स्मृति ब्लॉक प्राप्त कर सकता है बस अनुरोध किया। इसलिए जब आप अपनी स्मृति का केवल 10% उपयोग में हैं तो आपको आउट-ऑफ-मेमोरी त्रुटि मिलती है।

Arrays सबसे तेज़ चीज चल रही है, लेकिन आपको देखभाल के साथ बड़े लोगों का उपयोग करने की आवश्यकता है। प्रत्येक आवंटन और मुफ्त से अवगत रहें। उन्हें एक उचित प्रारंभिक आकार दें ताकि वे अंतरिक्ष को पुन: आवंटित नहीं कर सकें। (दिखाओ कि आप एक सी प्रोग्रामर हैं।) अपने जीसी के लिए दयालु रहें। यदि आपको बड़ी सूचियों को बनाना और मुक्त करना और आकार बदलना चाहिए, तो लिंक्ड क्लास का उपयोग करने पर विचार करें: लिंक्डलिस्ट, ट्रीमैप, कंसूरेंट लिंक्ड क्यूयू, आदि। वे केवल स्मृति की छोटी बिट्स का उपयोग करते हैं, और जीसी के उनसे प्यार करते हैं।

0

मैंने @Gray ने जो परीक्षण किया, उसका परीक्षण करने के लिए मैंने कुछ कोड बनाया। एक सरणी सूची से हटाते समय कॉपी कन्स्ट्रक्टर का उपयोग बनाई गई सूची में शून्य तत्वों की ओर जाता है। आप के रूप में गलत प्रविष्टियों की संख्या कोड का निम्न भाग में लगातार बढ़ जाती है यह देख सकते: यहां तक ​​कि

public static void main(String[] args) { 

    final int n = 1000000; 
    final int m = 100000; 
    final ArrayList<String> strings = new ArrayList<String>(n); 

    for(int i=0; i<n; i++) { 
     strings.add(new String("abc")); 
    } 


    Thread creatorThread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      ArrayList<String> stringsCme = new ArrayList<String>(strings); 
      int wrongEntries = 0; 
      for(int i=0; i<m; i++) { 
       stringsCme = new ArrayList<String>(strings); 

       for(String s : stringsCme) { 
        if(s == null || !s.equals("abc")) { 
         //System.out.println("Wrong entry: " + s); 
         wrongEntries++; 
        } 
       } 

       if(i % 100 == 0) 
        System.out.println("i = " + i + "\t list: " + stringsCme.size() + ", #wrong entries: " + wrongEntries); 
      } 

      System.out.println("#Wrong entries: " + wrongEntries); 
     } 
    }); 
    creatorThread.start(); 

    for(int i=0; i<m; i++) { 
     strings.remove(MathUtils.random(strings.size()-1)); 
    } 
} 
संबंधित मुद्दे