जावा में विभिन्न प्रकार के थ्रेड-सेफ सेट


135

जावा में थ्रेड-सुरक्षित सेट्स उत्पन्न करने के लिए बहुत सारे अलग-अलग कार्यान्वयन और तरीके प्रतीत होते हैं। कुछ उदाहरणों में शामिल हैं

1) CopyOnWriteArraySet

2) संग्रह। SynchronizedSet (सेट सेट)

3) समवर्तीSkipListSet

4) कलेक्शंस ।newSetFromMap (नया समवर्ती हाशम ())

5) अन्य सेट (4) के समान तरीके से उत्पन्न

ये उदाहरण Concurrency पैटर्न से आते हैं : जावा 6 में समवर्ती सेट कार्यान्वयन

क्या कोई कृपया इन उदाहरणों और दूसरों के अंतर, फायदे और नुकसान की व्याख्या कर सकता है? मुझे समझने में परेशानी हो रही है और Java Std डॉक्स से सब कुछ सीधे रखने में दिक्कत हो रही है।

जवाबों:


206

1) CopyOnWriteArraySetएक काफी सरल कार्यान्वयन है - इसमें मूल रूप से एक सरणी में तत्वों की एक सूची है, और जब सूची बदलते हैं, तो यह सरणी को कॉपी करता है। पाठकों और लेखकों के बीच तुल्यकालन की आवश्यकता से बचने के बावजूद, इस समय चल रहे Iterations और अन्य एक्सेस पुराने सरणी के साथ जारी हैं (हालांकि लेखन को खुद को सिंक्रनाइज़ करने की आवश्यकता है)। सामान्य रूप से तेजी से सेट ऑपरेशन (विशेष रूप से contains()) यहां काफी धीमा हैं, क्योंकि सरणियों को रैखिक समय में खोजा जाएगा।

इसे केवल वास्तव में छोटे सेटों के लिए उपयोग करें, जो अक्सर (पुनरावृत्त) पढ़ा जाएगा और शायद ही कभी बदल जाएगा। (स्विंगर्स श्रोता-सेट एक उदाहरण होगा, लेकिन ये वास्तव में सेट नहीं हैं, और इसका उपयोग केवल EDT से किया जाना चाहिए)

2) Collections.synchronizedSetबस मूल सेट के प्रत्येक विधि के चारों ओर एक सिंक्रनाइज़-ब्लॉक लपेटेंगे। आपको सीधे मूल सेट का उपयोग नहीं करना चाहिए। इसका मतलब यह है कि सेट के किसी भी दो तरीकों को समवर्ती रूप से निष्पादित नहीं किया जा सकता है (एक दूसरे को समाप्त होने तक ब्लॉक करेगा) - यह थ्रेड-सुरक्षित है, लेकिन अगर आपके पास कई थ्रेड वास्तव में सेट का उपयोग कर रहे हैं, तो आपके पास समवर्ती नहीं होगा। यदि आप इटरेटर का उपयोग करते हैं, तो आपको आमतौर पर इटर्टर कॉल के बीच सेट को संशोधित करते समय समवर्तीModificationException से बचने के लिए बाह्य रूप से सिंक्रनाइज़ करने की आवश्यकता होती है। प्रदर्शन मूल सेट के प्रदर्शन की तरह होगा (लेकिन कुछ सिंक्रनाइज़ेशन ओवरहेड के साथ, और यदि समवर्ती रूप से उपयोग किया जाता है तो अवरुद्ध होता है)।

इसका उपयोग करें यदि आपके पास केवल कम संगामिति है, और यह सुनिश्चित करना चाहते हैं कि सभी परिवर्तन तुरंत अन्य थ्रेड्स को दिखाई दें।

3) ConcurrentSkipListSetसमवर्ती SortedSetकार्यान्वयन है, जिसमें ओ (लॉग एन) में सबसे बुनियादी संचालन हैं। यह समवर्ती जोड़ने / हटाने और पढ़ने / पुनरावृत्ति की अनुमति देता है, जहां पुनरावृति इटेरेटर के निर्माण के बाद से परिवर्तनों के बारे में बता सकता है या नहीं बता सकता है। बल्क ऑपरेशन्स केवल एक सिंगल कॉल हैं, और एटोमिक रूप से नहीं - अन्य थ्रेड्स उनमें से कुछ का ही अवलोकन कर सकते हैं।

जाहिर है आप इसका उपयोग केवल तभी कर सकते हैं जब आपके पास अपने तत्वों पर कुछ कुल आदेश हो। यह उच्च-संगामिति स्थितियों के लिए एक आदर्श उम्मीदवार की तरह दिखता है, न कि बहुत बड़े सेट (ओ (लॉग एन) के कारण) के लिए।

4) ConcurrentHashMap(और इससे प्राप्त सेट) के लिए: यहाँ सबसे बुनियादी विकल्प हैं (औसतन, यदि आपके पास एक अच्छा और तेज़ है hashCode()) तो O (1) में (लेकिन O (n) को पतित कर सकते हैं), जैसे HashMap / HashSet। लिखने के लिए एक सीमित संगोष्ठी है (तालिका का विभाजन किया गया है, और लिखने की पहुंच आवश्यक विभाजन पर सिंक्रनाइज़ की जाएगी), जबकि रीड एक्सेस पूरी तरह से स्वयं और लेखन थ्रेड्स के समवर्ती है (लेकिन अभी तक हो रहे परिवर्तनों के परिणाम नहीं देख सकते हैं। लिखित)। इट्रेटर तब से बदलावों को देख सकता है या नहीं देख सकता है जब से इसे बनाया गया था, और थोक संचालन परमाणु नहीं हैं। आकार बदलना धीमा है (HashMap / HashSet के लिए), इस प्रकार निर्माण पर आवश्यक आकार का आकलन करके इससे बचने की कोशिश करें (और इसका 1/3 अधिक उपयोग करें, क्योंकि यह आकार 3/4 पूर्ण होने पर)।

जब आपके पास बड़े सेट हों, तो एक अच्छा (और तेज़) हैश फ़ंक्शन का उपयोग करें और मानचित्र बनाने से पहले सेट आकार और आवश्यक संगामिति का अनुमान लगा सकते हैं।

5) क्या अन्य समवर्ती मानचित्र कार्यान्वयन यहां उपयोग किए जा सकते हैं?


1
बस 1 में एक दृष्टि सुधार), नए सरणी में डेटा की प्रतिलिपि बनाने की प्रक्रिया को सिंक्रनाइज़ करके बंद किया जाना चाहिए। इसलिए, CopyOnWriteArraySet पूरी तरह से सिंक्रनाइज़ेशन की आवश्यकता से नहीं बचता है।
कप्तानहस्टिंग्स 13

पर ConcurrentHashMapआधारित सेट, "इस प्रकार निर्माण पर आवश्यक आकार का आकलन करके इस से बचने के लिए प्रयास करें।" जब आप सेट का आकार 75% से अधिक होता है, तब आप मानचित्र को जो आकार देते हैं, वह आपके अनुमान (या ज्ञात मूल्य) से 33% अधिक बड़ा होना चाहिए। मैं उपयोग करता हूंexpectedSize + 4 / 3 + 1
डैरन

@ मुझे लगता है कि पहले +का मतलब है एक *?
पाओलो एबरमन

@ Pa @loEbermann बेशक ... यह होना चाहिएexpectedSize * 4 / 3 + 1
डैरन

1
जावा 8 के लिए ConcurrentMap(या HashMap) यदि एक ही बाल्टी में मैपिंग की संख्या थ्रेशोल्ड वैल्यू तक पहुँचती है (मेरा मानना ​​है कि यह 16 है) तो सूची बाइनरी सर्च ट्री में बदल जाती है (लाल-काला पेड़ को पहले से तैयार किया जाता है) और उस स्थिति में देखें समय होगा O(lg n)और नहीं O(n)
akhil_mittal

20

प्रत्येक संशोधन पर पूरे सेट को बदलने और बदलने के द्वारा , संगामिति-संबंधित गुणों contains()के HashSetसाथ प्रदर्शन को जोड़ना संभव है ।CopyOnWriteArraySetAtomicReference<Set>

कार्यान्वयन स्केच:

public abstract class CopyOnWriteSet<E> implements Set<E> {

    private final AtomicReference<Set<E>> ref;

    protected CopyOnWriteSet( Collection<? extends E> c ) {
        ref = new AtomicReference<Set<E>>( new HashSet<E>( c ) );
    }

    @Override
    public boolean contains( Object o ) {
        return ref.get().contains( o );
    }

    @Override
    public boolean add( E e ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( current.contains( e ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.add( e );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

    @Override
    public boolean remove( Object o ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( !current.contains( o ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.remove( o );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

}

वास्तव AtomicReferenceमें मूल्य अस्थिर है। इसका अर्थ है कि यह सुनिश्चित करता है कि कोई भी धागा बासी डेटा को नहीं पढ़ रहा है और happens-beforeगारंटी प्रदान करता है क्योंकि कोड कंपाइलर द्वारा पुन: व्यवस्थित नहीं किया जा सकता है। लेकिन अगर केवल प्राप्त / निर्धारित तरीकों का AtomicReferenceउपयोग किया जाता है तो हम वास्तव में अपने चर को एक फैंसी तरीके से चिह्नित कर रहे हैं।
akhil_mittal

इस उत्तर को पर्याप्त नहीं बनाया जा सकता है क्योंकि (1) जब तक मैं कुछ याद नहीं करता, यह सभी प्रकार के संग्रह के लिए काम करेगा (2) अन्य वर्गों में से कोई भी एक बार में संपूर्ण संग्रह को परमाणु रूप से अद्यतन करने का एक तरीका प्रदान नहीं करता है ... यह बहुत उपयोगी है ।
जिली

मैंने इस शब्दशः को लागू करने की कोशिश की, लेकिन यह पाया गया कि इसे लेबल किया गया था abstract, प्रतीत होता है कि कई तरीकों को लिखने से बचने के लिए। मैंने उन्हें जोड़ने के बारे में सेट किया, लेकिन साथ एक सड़क पर भाग गया iterator()। मुझे नहीं पता कि बिना मॉडल को तोड़े इस बात पर एक पुनरावृत्ति कैसे बनाए रखी जाए। लगता है कि मुझे हमेशा से गुजरना पड़ता है ref, और हर बार एक अलग अंतर्निहित सेट मिल सकता है, जिसके लिए अंतर्निहित सेट पर एक नया पुनरावृत्ति प्राप्त करने की आवश्यकता होती है, जो मेरे लिए बेकार है, क्योंकि यह आइटम शून्य से शुरू होगा। कोई अंतर्दृष्टि?
14

ठीक है, मुझे लगता है कि गारंटी है, प्रत्येक ग्राहक को समय में एक निश्चित स्नैपशॉट मिल रहा है, इसलिए अंतर्निहित संग्रह का इटरेटर ठीक काम करेगा यदि आपको इसकी आवश्यकता है। मेरा उपयोग मामला प्रतिस्पर्धा के थ्रेड्स को व्यक्तिगत संसाधनों में "दावा" करने की अनुमति देने के लिए है, और यदि वे सेट के विभिन्न संस्करण हैं तो यह काम नहीं करेगा। हालांकि, दूसरे पर ... मुझे लगता है कि मेरे धागे को बस एक नया पुनरावृत्ति प्राप्त करने की आवश्यकता है और फिर से कोशिश करें यदि CopyOnWriteSet.remove (चुना_item) गलत हो जाता है ... जो इसे परवाह किए बिना करना होगा :)
nclark

11

यदि Javadocs मदद नहीं करता है, तो संभवतः आपको डेटा संरचनाओं के बारे में पढ़ने के लिए बस एक पुस्तक या लेख खोजना चाहिए। एक नजर में:

  • CopyOnWriteArraySet आपके द्वारा संग्रह को म्यूट करने पर हर बार अंतर्निहित सरणी की एक नई प्रतिलिपि बनाता है, इसलिए लिखते हैं कि धीमे हैं और Iterators तेज़ और सुसंगत हैं।
  • संग्रह थ्रेड्स कैफे बनाने के लिए पुराने-स्कूल सिंक्रनाइज़ विधि कॉल का उपयोग करता है। यह एक कम प्रदर्शन करने वाला संस्करण होगा।
  • ConcurrentSkipListSet प्रस्तुतकर्ता असंगत बैच संचालन (addAll, removeAll, आदि) और Iterators के साथ लिखता है।
  • Collections.newSetFromMap (new ConcurrentHashMap ()) में समवर्ती HashMap का शब्दार्थ है, जो मेरा मानना ​​है कि जरूरी नहीं कि वह पढ़े या लिखे के लिए अनुकूलित हो, लेकिन ConcurrentSkipListSet की तरह, असंगत बैच ऑपरेशन है।

1
developer.com/java/article.php/10922_3829891_2/… <एक किताब से भी बेहतर)
ycomp

1

कमजोर संदर्भों का समवर्ती सेट

एक और मोड़ कमजोर संदर्भों का एक थ्रेड-सुरक्षित सेट है ।

ऐसा सेट पब-उप परिदृश्य में ग्राहकों की ट्रैकिंग के लिए आसान है। जब एक ग्राहक अन्य स्थानों पर दायरे से बाहर हो रहा है, और इसलिए कचरा-संग्रह के लिए एक उम्मीदवार बनने की ओर अग्रसर है, तो ग्राहक को इनायत से सदस्यता लेने से परेशान होने की आवश्यकता नहीं है। कमजोर संदर्भ ग्राहक को कचरा-संग्रह के लिए एक उम्मीदवार होने के लिए अपने संक्रमण को पूरा करने की अनुमति देता है। जब कचरा अंततः एकत्र किया जाता है, तो सेट में प्रवेश को हटा दिया जाता है।

जबकि इस तरह का कोई सेट सीधे बंडल किए गए वर्गों के साथ प्रदान नहीं किया गया है, आप कुछ कॉल के साथ एक बना सकते हैं।

पहले हम वर्ग का Setलाभ उठाकर कमजोर संदर्भ बनाना शुरू करते हैं WeakHashMap। इसके लिए वर्ग प्रलेखन में दिखाया गया है Collections.newSetFromMap

Set< YourClassGoesHere > weakHashSet = 
    Collections
    .newSetFromMap(
        new WeakHashMap< YourClassGoesHere , Boolean >()
    )
;

नक्शे का मानBoolean , अप्रासंगिक है, क्योंकि मानचित्र की कुंजी हमारा निर्माण करती है Set

पब-सब जैसे परिदृश्य में, हमें थ्रेड-सेफ्टी की जरूरत होती है, अगर सब्सक्राइबर और पब्लिशर्स अलग-अलग थ्रेड्स पर काम कर रहे हों (जो कि संभवतया मामला है)।

इस सेट थ्रेड को सुरक्षित बनाने के लिए एक सिंक्रनाइज़ सेट के रूप में लपेटकर एक कदम आगे बढ़ें। करने के लिए एक कॉल में फ़ीड Collections.synchronizedSet

this.subscribers =
        Collections.synchronizedSet(
                Collections.newSetFromMap(
                        new WeakHashMap <>()  // Parameterized types `< YourClassGoesHere , Boolean >` are inferred, no need to specify.
                )
        );

अब हम अपने परिणामी ग्राहकों को जोड़ सकते हैं और हटा सकते हैं Set। और किसी भी "गायब" ग्राहकों को अंततः कचरा-संग्रह के निष्पादन के बाद स्वचालित रूप से हटा दिया जाएगा। जब यह निष्पादन होता है, तो यह आपके जेवीएम के कचरा-संग्राहक कार्यान्वयन पर निर्भर करता है, और फिलहाल रनटाइम स्थिति पर निर्भर करता है। कब और कैसे अंतर्निहित WeakHashMapएक्सपायर प्रविष्टियों को क्लियर किया जाता है , इस पर चर्चा और उदाहरण के लिए , यह प्रश्न देखें, * क्या WeakHashMap कभी-बढ़ता है, या क्या यह कचरा कुंजियों को साफ करता है? *

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