जावा: सभी पर फिर से सूचित करें () बनाम नोटिफिकेशन ()


377

यदि कोई " notify()और के बीच अंतर" के लिए एक Googles करता है, notifyAll()तो बहुत सारे स्पष्टीकरण सामने आएंगे (जावेदोक पैराग्राफ को छोड़कर)। यह सभी जाग्रत होने वाले धागों की संख्या के बराबर होता है: एक में notify()और सभी में notifyAll()

हालाँकि (अगर मैं इन तरीकों के बीच अंतर को सही समझता हूँ), केवल एक धागा हमेशा आगे की निगरानी अधिग्रहण के लिए चुना जाता है; पहले मामले में VM द्वारा चयनित एक, दूसरे मामले में सिस्टम थ्रेड अनुसूचक द्वारा चयनित एक मामले में। उन दोनों के लिए सटीक चयन प्रक्रियाएं (सामान्य स्थिति में) प्रोग्रामर को ज्ञात नहीं हैं।

सूचित () और InformAll () के बीच उपयोगी अंतर क्या है ? क्या मैं कुछ भूल रहा हूँ?


6
संगामिति के लिए उपयोग करने के लिए उपयोगी पुस्तकालयों संगामिति पुस्तकालयों में हैं। मेरा सुझाव है कि ये लगभग हर मामले में बेहतर विकल्प हैं। कॉन्सुरेंसी लाइब्रेरी की पूर्व तिथि जावा 5.0 (जिसमें वे 2004 में मानक के रूप में जोड़े गए थे)
पीटर लॉरी

4
मैं पीटर से असहमत हूं। जावा में संगामिति पुस्तकालय कार्यान्वित किया जाता है, और जब भी आप लॉक (), अनलॉक (), आदि कॉल करते हैं तो हर बार निष्पादित जावा कोड का एक बहुत कुछ होता है, आप synchronizedकुछ अच्छे को छोड़कर, अच्छे पुराने के बजाय संगामिति पुस्तकालय का उपयोग करके अपने आप को गोली मार सकते हैं। , बल्कि दुर्लभ मामलों का उपयोग करें।
अलेक्जेंडर रियाज़ोव

2
मुख्य गलतफहमी यह प्रतीत होती है: ... केवल एक धागा हमेशा आगे की निगरानी अधिग्रहण के लिए चुना जाता है; पहले मामले में VM द्वारा चयनित एक, दूसरे मामले में सिस्टम थ्रेड शेड्यूलर द्वारा चयनित एक मामला है। निहितार्थ यह है कि अनिवार्य रूप से समान हैं। हालांकि जैसा कि वर्णित व्यवहार सही है, जो याद आ रही है वह यह है कि notifyAll()मामले में, पहले जागने के बाद अन्य धागे एक-एक करके मॉनीटर हासिल कर लेंगे। में notifyमामला है, अन्य थ्रेड से कोई भी जगाया जाता है। इसलिए कार्यात्मक रूप से वे बहुत अलग हैं!
BeeOnRope

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

@ चेतनगौड़ा सभी थ्रेड्स को सूचित करते हुए सूचित करते हैं कि वास्तव में केवल एक ही मनमाने धागे में महत्वपूर्ण अंतर है, जब तक कि सूक्ष्म रूप से महत्वपूर्ण लेकिन महत्वपूर्ण अंतर नहीं होता है, तब तक हम आपको सूचित करते हैं। (केवल 1 थ्रेड), सभी अन्य थ्रेड प्रतीक्षा में होंगे जब तक कि यह एक स्पष्ट अधिसूचना प्राप्त नहीं करता है। / संकेत। सभी को सूचित करते हुए, सभी थ्रेड्स को निष्पादित किया जाएगा और किसी भी अन्य अधिसूचना के बिना एक के बाद एक किसी न किसी क्रम में पूरा किया जाएगा - यहां हमें यह कहना चाहिए कि थ्रेड्स हैं blockedऔर नहीं waiting। जब blockedतक कि एक थ्रेड syncब्लॉक के अंदर नहीं होता तब तक इसका निष्पादन अस्थायी रूप से निलंबित कर दिया जाता है ।
user104309

जवाबों:


248

हालाँकि (अगर मैं इन तरीकों के बीच अंतर को सही समझता हूँ), केवल एक धागा हमेशा आगे की निगरानी अधिग्रहण के लिए चुना जाता है।

वह सही नहीं है। o.notifyAll()जाग सभी धागे कि में ब्लॉक किए गए हैं की o.wait()कॉल। थ्रेड्स को केवल o.wait()एक-एक करके लौटने की अनुमति है , लेकिन वे प्रत्येक को अपनी बारी मिलेगी।


सीधे शब्दों में कहें, तो यह इस बात पर निर्भर करता है कि आपके सूत्र अधिसूचित होने की प्रतीक्षा क्यों कर रहे हैं। क्या आप प्रतीक्षा के थ्रेड्स में से एक को बताना चाहते हैं कि कुछ हुआ था, या क्या आप एक ही समय में उन सभी को बताना चाहते हैं?

कुछ मामलों में, प्रतीक्षा समाप्त होने के बाद सभी प्रतीक्षा धागे उपयोगी कार्रवाई कर सकते हैं। एक उदाहरण थ्रेड्स का एक सेट होगा जो एक निश्चित कार्य के समाप्त होने की प्रतीक्षा कर रहा है; एक बार कार्य समाप्त हो जाने के बाद, सभी प्रतीक्षा धागे उनके व्यवसाय के साथ जारी रह सकते हैं। ऐसे मामले में आप एक ही समय में सभी प्रतीक्षा सूत्र को जगाने के लिए InformAll () का उपयोग करेंगे ।

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


कई मामलों में, हालत का इंतजार करने का कोड एक लूप के रूप में लिखा जाएगा:

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

इस तरह, यदि एक o.notifyAll()कॉल एक से अधिक प्रतीक्षा धागे o.wait()को जगाती है , और पहले से ही वापस आती है , तो स्थिति को झूठी स्थिति में छोड़ देता है, फिर अन्य धागे जो जागृत थे, वापस प्रतीक्षा में जाएंगे।


29
यदि आप सिर्फ एक थ्रेड को सूचित करते हैं, लेकिन एक ऑब्जेक्ट पर कई प्रतीक्षा कर रहे हैं, तो वीएम कैसे निर्धारित करता है कि किसे सूचित करना है?
उभयचर

6
मैं जावा युक्ति के बारे में निश्चित रूप से नहीं कह सकता, लेकिन आम तौर पर आपको इस तरह के विवरण के बारे में धारणा बनाने से बचना चाहिए। मुझे लगता है कि आप यह मान सकते हैं कि VM इसे एक समझदार और ज्यादातर उचित तरीके से करेगा, हालांकि।
Liedman 12

15
Liedman गंभीर रूप से गलत है, जावा विनिर्देश स्पष्ट रूप से बताता है कि अधिसूचित () उचित होने की गारंटी नहीं है। यानी प्रत्येक कॉल को सूचित करने के लिए एक ही धागा फिर से जा सकता है (मॉनिटर में धागा कतार FAIR या फीफो नहीं है)। हालांकि अनुसूचक निष्पक्ष होने की गारंटी है। यही कारण है कि ज्यादातर मामलों में जहां आपके पास 2 से अधिक धागे हैं, आपको सूचित करना चाहिए।
यमन टीएम

45
@YTTM मैं सभी रचनात्मक आलोचना के लिए हूं, लेकिन मुझे लगता है कि आपका लहजा थोड़ा अनुचित है। मैंने स्पष्ट रूप से कहा "कुछ के लिए नहीं कह सकता" और "मुझे लगता है"। आसानी से, क्या आपने कभी सात साल पहले कुछ लिखा था जो 100% सही नहीं था?
लीदमैन

10
समस्या यह है कि यह स्वीकृत उत्तर है, यह व्यक्तिगत गर्व का सवाल नहीं है। यदि आप जानते हैं कि आप अब गलत थे, तो कृपया इसे कहने के लिए अपना उत्तर संपादित करें, और उदाहरण के लिए xagyg शैक्षणिक और नीचे दिए गए सही उत्तर को इंगित करें।
यमन टीएम

330

जाहिर है, notifyवेट (किसी भी) एक सेट notifyAllवेट सेट में, सभी थ्रेड्स को वेटिंग सेट में जगाता है। निम्नलिखित चर्चा को किसी भी संदेह को स्पष्ट करना चाहिए। notifyAllज्यादातर समय इस्तेमाल किया जाना चाहिए। यदि आप सुनिश्चित नहीं हैं कि notifyAllकिसका उपयोग करना है, तो उपयोग करें। कृपया स्पष्टीकरण देखें जो निम्न प्रकार है।

बहुत ध्यान से पढ़ें और समझें। यदि आपके कोई प्रश्न हैं, तो कृपया मुझे एक ईमेल भेजें।

निर्माता / उपभोक्ता को देखें (धारणा दो तरीकों के साथ एक निर्माता-निर्माता वर्ग है)। IT BROKEN (क्योंकि यह उपयोग करता है notify) - हाँ यह काम करता है - यहां तक ​​कि ज्यादातर समय, लेकिन यह गतिरोध भी पैदा कर सकता है - हम देखेंगे क्यों:

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

पहले तो,

हमें प्रतीक्षा के दौरान थोड़ी देर के लूप की आवश्यकता क्यों है?

whileइस स्थिति को प्राप्त करने के लिए हमें एक लूप की आवश्यकता है:

उपभोक्ता 1 (C1) सिंक्रनाइज़ ब्लॉक में प्रवेश करता है और बफर खाली होता है, इसलिए C1 को प्रतीक्षा सेट ( waitकॉल के माध्यम से ) में रखा जाता है। कंज्यूमर 2 (C2) सिंक्रोनाइज़ किए गए तरीके (बिंदु Y ऊपर) दर्ज करने वाला है, लेकिन निर्माता P1 बफर में एक ऑब्जेक्ट डालता है, और बाद में कॉल करता है notify। एकमात्र प्रतीक्षा धागा C1 है, इसलिए इसे जगाया जाता है और अब बिंदु X (ऊपर) पर ऑब्जेक्ट लॉक को फिर से प्राप्त करने का प्रयास करता है।

अब C1 और C2 सिंक्रोनाइज़ेशन लॉक का अधिग्रहण करने का प्रयास कर रहे हैं। उनमें से एक (nondeterministically) चुना जाता है और विधि में प्रवेश करता है, दूसरे को अवरुद्ध किया जाता है (प्रतीक्षा नहीं - लेकिन अवरुद्ध, विधि पर ताला प्राप्त करने की कोशिश कर रहा है)। मान लीजिए कि C2 को पहले ताला मिला है। C1 अभी भी अवरुद्ध है (X पर लॉक को प्राप्त करने की कोशिश कर रहा है)। C2 विधि को पूरा करता है और लॉक जारी करता है। अब, C1 लॉक का अधिग्रहण करता है। लगता है क्या, भाग्यशाली है कि हमारे पास एक whileलूप है, क्योंकि, C1 लूप चेक (गार्ड) करता है और बफर से एक गैर-मौजूद तत्व को हटाने से रोका जाता है (C2 पहले से ही मिल गया है!)। यदि हमारे पास नहीं था while, तो हमें IndexArrayOutOfBoundsExceptionबफर के पहले तत्व को हटाने की कोशिश करने वाला C1 मिलेगा !

अभी,

ठीक है, अब हमें InformAll की आवश्यकता क्यों है?

ऊपर के निर्माता / उपभोक्ता उदाहरण में ऐसा लगता है कि हम दूर हो सकते हैं notify। यह इस तरह लगता है, क्योंकि हम साबित कर सकते हैं कि निर्माता और उपभोक्ता के लिए प्रतीक्षा छोरों पर गार्ड पारस्परिक रूप से अनन्य हैं। यही है, ऐसा लगता है कि हमारे पास putविधि के साथ-साथ विधि में प्रतीक्षा करने वाला एक धागा नहीं हो सकता है get, क्योंकि, इसके लिए सत्य होना है, तो निम्नलिखित को सच होना होगा:

buf.size() == 0 AND buf.size() == MAX_SIZE (मान लें कि MAX_SIZE 0 नहीं है)

फिर भी, यह काफी अच्छा नहीं है, हम उपयोग करने की आवश्यकता है notifyAll। आइए देखें क्यों ...

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

STEP 1:
- P1 बफर में 1 char डालता है

चरण 2:
- पी 2 प्रयास put- चेक लूप लूप - पहले से ही एक चार - प्रतीक्षा करता है

चरण 3:
- पी 3 प्रयास put- चेक लूप लूप - पहले से ही एक चार - प्रतीक्षा करता है

चरण 4:
- C1 1 वर्ण प्राप्त करने का प्रयास करता है
- C2 getविधि में प्रवेश पर
1 वर्ण - ब्लॉक प्राप्त करने का प्रयास करता है - C3 1 वर्ण प्राप्त करने का प्रयास करता है - getविधि में प्रवेश पर ब्लॉक

चरण 5:
- C1 getविधि को निष्पादित कर रहा है - चार हो जाता है, कॉल करता है notify, बाहर निकलता है विधि
- notifyP2
से पहले उठता है - B2 , C2 विधि से पहले प्रवेश कर सकता है P2 (लॉक को पुनः प्राप्त करना चाहिए), इसलिए putपद्धति पर प्रविष्टि पर P2 ब्लॉक
- C2 प्रतीक्षा लूप की जांच करता है, बफर में कोई और अधिक चार्ट नहीं है, इसलिए इंतजार करता है
- C3 C2 के बाद विधि में प्रवेश करता है, लेकिन P2 से पहले, प्रतीक्षा लूप की जांच करता है, बफर में और अधिक वर्ण नहीं है, इसलिए इंतजार करता है

चरण 6:
- अब: पी 3, सी 2 और सी 3 प्रतीक्षा है!
- अंत में पी 2 लॉक को अधिग्रहित करता है, बफर में एक चार डालता है, कॉल सूचित करता है, विधि से बाहर निकलता है

चरण 7:
- पी 2 के नोटिफिकेशन पी 3 (किसी भी धागे को जाग्रत किया जा सकता है याद रखें)
- पी 3 प्रतीक्षा लूप स्थिति की जांच करता है, बफर में पहले से ही एक चर है, इसलिए इंतजार करता है।
- अधिक नोटों का उल्लेख नहीं करने के लिए और तीन साल के लिए हमेशा की तरह तैयार!

समाधान: निर्माता / उपभोक्ता कोड (ऊपर) के notifyसाथ बदलें notifyAll


1
finnw - P3 को पुन: जांच करनी चाहिए क्योंकि notifyP3 (इस उदाहरण में चयनित धागा) उस बिंदु से आगे बढ़ने का कारण बनता है जिसका वह इंतजार कर रहा था (यानी whileलूप के भीतर )। ऐसे अन्य उदाहरण हैं जो गतिरोध का कारण नहीं हैं, हालांकि, इस मामले में उपयोग notifyगतिरोध-मुक्त कोड की गारंटी नहीं देता है। का उपयोग notifyAllकरता है।
xagyg

4
@marcus बहुत करीब। InformAll के साथ, प्रत्येक थ्रेड लॉक (एक समय में एक) को फिर से लिंक करेगा, लेकिन ध्यान दें कि एक थ्रेड ने लॉक को फिर से प्राप्त किया और विधि को निष्पादित किया (और फिर बाहर निकल गया) ... अगला धागा लॉक को फिर से प्राप्त करता है, "जबकि" चेक करता है और "प्रतीक्षा" पर वापस चला जाएगा (यह इस पर निर्भर करता है कि स्थिति क्या है)। तो, एक धागे को सूचित करें - जैसा कि आप सही ढंग से राज्य करते हैं। InformAll सभी थ्रेड्स को जाग्रत करता है और प्रत्येक थ्रेड एक बार में लॉक को फिर से प्राप्त करता है - "जबकि" की स्थिति की जांच करता है और या तो विधि को निष्पादित करता है या "फिर से इंतजार करता है"।
xagyg

1
@xagyg, क्या आप ऐसे परिदृश्य के बारे में बात कर रहे हैं जिसमें प्रत्येक निर्माता के पास स्टोर करने के लिए केवल एक ही चार हो? यदि हां, तो आपका उदाहरण सही है, लेकिन बहुत दिलचस्प आईएमओ नहीं है। मेरे द्वारा सुझाए गए अतिरिक्त चरणों के साथ, आप एक ही सिस्टम को गतिरोध में डाल सकते हैं, लेकिन एक अनबाउंड इनपुट के साथ - जो इस तरह के पैटर्न आमतौर पर वास्तविकता में उपयोग किया जा रहा है।
एरन

3
@codeObserver आपने पूछा था: "कॉलिंग InformAll () को एक ही समय में () स्थिति की जाँच करते हुए कई प्रतीक्षा थ्रेड की ओर ले जाएगा .. और इसलिए संभावना है कि इससे पहले कि saitsfied हो, 2 थ्रेड्स पहले से ही इससे बाहर हैं। अपवाद? " नहीं, यह संभव नहीं है, हालांकि कई सूत्र जागेंगे, वे एक ही समय में स्थिति की जांच नहीं कर सकते। कोड सेक्शन में दोबारा प्रवेश करने से पहले और फिर से जांचने से पहले वे प्रत्येक को लॉक (प्रतीक्षा के तुरंत बाद) प्राप्त करने की आवश्यकता होती है। इसलिए, एक समय में।
xagyg

4
@xagyg अच्छा उदाहरण है। यह मूल प्रश्न से हटकर विषय है; सिर्फ चर्चा के लिए। गतिरोध एक डिज़ाइन समस्या imo है (यदि मैं गलत हूं तो मुझे सुधारें)। क्योंकि आपके पास पुट और गेट दोनों द्वारा साझा किया गया एक लॉक है। और JVM स्मार्ट नहीं है जो पुट रिलीज़ के बाद पुट को लॉक और वाइस पद्य देता है। डेड लॉक इसलिए होता है क्योंकि पुट वेक अप डालते हैं, जो अपने आप को इंतजार करने के लिए वापस रख देता है () जबकि () के कारण। दो वर्गों (और दो ताले) बनाने का काम करेगा? इसलिए {synchonized (get)} लगाएं, {{synchonized (put)} प्राप्त करें। दूसरे शब्दों में, get put put only, और put put get only मिलेंगे।
Jay

43

उपयोगी अंतर:

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

  • अन्य मामलों के लिए InformAll () का उपयोग करें जहाँ प्रतीक्षा करने के लिए अलग-अलग उद्देश्य हो सकते हैं और समवर्ती चलाने में सक्षम होना चाहिए। एक उदाहरण साझा संसाधन पर एक रखरखाव ऑपरेशन है, जहां संसाधन तक पहुंचने से पहले कई थ्रेड्स ऑपरेशन के पूरा होने की प्रतीक्षा कर रहे हैं।


19

मुझे लगता है कि यह इस बात पर निर्भर करता है कि संसाधनों का उत्पादन और उपभोग कैसे किया जाता है। यदि 5 वर्क ऑब्जेक्ट्स एक ही बार में उपलब्ध हैं और आपके पास 5 उपभोक्ता ऑब्जेक्ट हैं, तो यह नोटिफिकेशन () का उपयोग करके सभी थ्रेड्स को जगाने के लिए समझ में आएगा ताकि प्रत्येक 1 कार्य ऑब्जेक्ट को प्रोसेस कर सके।

यदि आपके पास बस एक काम की वस्तु उपलब्ध है, तो उस एक वस्तु की दौड़ के लिए सभी उपभोक्ता वस्तुओं को जगाने का क्या मतलब है? उपलब्ध कार्य के लिए पहले जाँच करने वाले को यह मिल जाएगा और अन्य सभी धागे जाँचेंगे और पाएंगे कि उनके पास करने के लिए कुछ नहीं है।

मुझे यहां एक बहुत अच्छा स्पष्टीकरण मिला । संक्षेप में:

सूचित () विधि का उपयोग आमतौर पर संसाधन पूलों के लिए किया जाता है , जहां "उपभोक्ताओं" या "श्रमिकों" की एक मनमानी संख्या होती है, जो संसाधन लेते हैं, लेकिन जब पूल में एक संसाधन जोड़ा जाता है, तो केवल एक इंतजार कर रहे उपभोक्ता या श्रमिक ही सौदा कर सकते हैं इसके साथ। InformAll () विधि वास्तव में अधिकांश अन्य मामलों में उपयोग की जाती है। कड़ाई से, एक शर्त के वेटर को सूचित करना आवश्यक है जो कई वेटर को आगे बढ़ने की अनुमति दे सकता है। लेकिन यह जानना अक्सर मुश्किल होता है। इसलिए एक सामान्य नियम के रूप में, यदि आपके पास सूचित () का उपयोग करने के लिए कोई विशेष तर्क नहीं है, तो आपको संभवतः InformAll () का उपयोग करना चाहिए , क्योंकि यह जानना अक्सर मुश्किल होता है कि किसी विशेष वस्तु पर थ्रेड्स का इंतजार क्या होगा और क्यों।


11

ध्यान दें कि संगामिति उपयोगिताओं के साथ आपके पास भी विकल्प हैं signal()और signalAll()जैसे कि इन विधियों को वहां बुलाया जाता है। अतः प्रश्न इसके साथ भी मान्य है java.util.concurrent

डौग ली को लाता है अपने में एक दिलचस्प बात प्रसिद्ध पुस्तक : अगर एक notify()और Thread.interrupt()एक ही समय में होता है, को सूचित वास्तव में खो जाना हो सकता है। यदि ऐसा हो सकता है और नाटकीय प्रभाव पड़ सकता notifyAll()है, तो आप एक सुरक्षित विकल्प है, भले ही आप ओवरहेड की कीमत चुकाते हैं (ज्यादातर समय बहुत सारे धागे जगाते हैं)।


10

संक्षिप्त सारांश:

जब तक आपके पास एक व्यापक समानांतर अनुप्रयोग नहीं होता है, जहां सभी सूचनाएँ एक समान काम करती हैं, तब तक हमेशा InformAll () को सूचित करें () पसंद करें

स्पष्टीकरण:

सूचित करें () [...] एक एकल धागा जगाता है। क्योंकि अधिसूचित () आपको उस धागे को निर्दिष्ट करने की अनुमति नहीं देता है जो जाग गया है, यह केवल बड़े पैमाने पर समानांतर अनुप्रयोगों में उपयोगी है - अर्थात्, बड़ी संख्या में थ्रेड्स के साथ प्रोग्राम, सभी समान काम कर रहे हैं। इस तरह के एक आवेदन में, आपको परवाह नहीं है कि कौन सा धागा जाग जाता है।

स्रोत: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

की तुलना करें सूचित () के साथ notifyAll () में वर्णित स्थिति से ऊपर: जहां धागे ही बात कर रहे एक व्यापक समानांतर आवेदन। यदि आप उस स्थिति में InformAll () को कॉल करते हैं , तो InformAll () बड़ी संख्या में थ्रेड्स के जागने (यानी शेड्यूलिंग ) को उत्पन्न करेगा, उनमें से कई अनावश्यक रूप से (केवल एक थ्रेड वास्तव में आगे बढ़ सकते हैं, अर्थात वह धागा जो प्रदान किया जाएगा) ऑब्जेक्ट प्रतीक्षा के लिए मॉनिटर () , सूचित () , या नोटिफिकेशन () पर कॉल किया गया था, इसलिए कंप्यूटिंग संसाधनों को बर्बाद कर रहा है।

इस प्रकार, यदि आपके पास कोई एप्लिकेशन नहीं है जहां बड़ी संख्या में थ्रेड समवर्ती रूप से कार्य करते हैं, तो InformAll () को सूचित करें () करें । क्यों? क्योंकि, जैसा कि अन्य उपयोगकर्ता पहले ही इस फोरम में उत्तर दे चुके हैं, सूचित करें ()

इस ऑब्जेक्ट के मॉनीटर पर प्रतीक्षा कर रहा है एक एकल थ्रेड उठता है। [...] विकल्प मनमाना है और कार्यान्वयन के विवेक पर होता है।

स्रोत: जावा एसई 8 एपीआई ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- )

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

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


9

यहाँ एक उदाहरण है। चलाओ। फिर एक नोटिफ़िकेशन () को नोटिफ़िकेशन () में बदलें और देखें कि क्या होता है।

निर्माताकॉनसमर। नमूना वर्ग

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

ड्रॉपबॉक्स वर्ग

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

उपभोक्ता वर्ग

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

निर्माता वर्ग

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

8

जोशुआ बलोच से, प्रभावी जावा 2 संस्करण में जावा गुरु स्वयं:

"आइटम 69: प्रतीक्षा करने और सूचित करने के लिए संगामिति उपयोगिताओं को प्राथमिकता दें"।


16
क्यों स्रोत से अधिक महत्वपूर्ण है।
पचेरियर

2
@Pacerier वेल ने कहा। मुझे इसके कारणों का पता लगाने में अधिक दिलचस्पी होगी। एक संभावित कारण यह हो सकता है कि ऑब्जेक्ट क्लास में प्रतीक्षा और सूचना एक अंतर्निहित स्थिति चर पर आधारित है। इसलिए मानक निर्माता और उपभोक्ता उदाहरण में ..... उत्पादक और उपभोक्ता दोनों उसी स्थिति में प्रतीक्षा करेंगे जिसके कारण उनके जवाब में xagyg द्वारा समझाया गया गतिरोध उत्पन्न हो सकता है। में समझाया तो एक बेहतर दृष्टिकोण 2 हालत चर का उपयोग करने के लिए है docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/...
राहुल

6

मुझे उम्मीद है कि इससे कुछ संदेह दूर होंगे।

नोटिफ़िकेशन () : नोटिफ़िकेशन () विधि लॉक के लिए प्रतीक्षा कर रहे एक थ्रेड को जागृत करता है (पहला थ्रेड जिसे उस लॉक पर प्रतीक्षा () कहा जाता है)।

InformAll () : InformAll () विधि लॉक की प्रतीक्षा कर रहे सभी थ्रेड्स को जगाती है; जेवीएम थ्रेड्स की सूची में से एक धागे का चयन करता है जो लॉक का इंतजार करता है और उस धागे को जगाता है।

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

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

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


6

एक धागे के लिए तीन अवस्थाएँ होती हैं।

  1. WAIT - थ्रेड किसी भी CPU चक्र का उपयोग नहीं कर रहा है
  2. बंद - धागा एक मॉनिटर प्राप्त करने की कोशिश कर अवरुद्ध है। यह अभी भी सीपीयू चक्र का उपयोग कर सकता है
  3. रनिंग - धागा चल रहा है।

अब, जब एक अधिसूचित () कहा जाता है, तो JVM एक धागा चुनता है और उन्हें BLOCKED अवस्था में ले जाता है और इसलिए RUNNING राज्य में जाता है क्योंकि मॉनिटर ऑब्जेक्ट के लिए कोई प्रतियोगिता नहीं है।

जब एक InformAll () को कॉल किया जाता है, तो JVM सभी थ्रेड्स चुनता है और उन सभी को BLOCKED स्टेट में ले जाता है। इन सभी थ्रेड्स को प्राथमिकता के आधार पर ऑब्जेक्ट का लॉक मिलेगा। इसके बाद जो मॉनिटर को अधिग्रहित करने में सक्षम है, वह पहले और बाद में रनिंग स्टेट पर जा सकेगा।


बस कमाल की व्याख्या।
रयातिरेक

5

मैं बहुत हैरान हूं कि किसी ने भी कुख्यात "खोई हुई वेकअप" समस्या (इसे Google) का उल्लेख नहीं किया है।

मूल रूप से:

  1. यदि आपके पास एक ही शर्त पर कई धागे हैं और,
  2. कई धागे जो आपको राज्य ए से राज्य बी और, से संक्रमण कर सकते हैं और
  3. कई धागे जो आपको राज्य बी से राज्य ए में संक्रमण कर सकते हैं (आमतौर पर 1 के रूप में एक ही धागे) और,
  4. राज्य ए से बी तक संक्रमण 1 में धागे को सूचित करना चाहिए।

जब तक आपको यह साबित न हो जाए कि आपको गारंटीकृत गारंटियों की जरूरत नहीं है, तब तक आपको नोटिफिकेशन का उपयोग करना चाहिए।

एक सामान्य उदाहरण एक समवर्ती FIFO कतार है जहां: कई एन्केयर्स (1. और 3. ऊपर) आपकी कतार को खाली से गैर-रिक्त एकाधिक डेकेयर्स (2. ऊपर) में परिवर्तित कर सकते हैं, इस शर्त का इंतजार कर सकते हैं कि "कतार खाली नहीं है" -> गैर-रिक्त को dequeuers को सूचित करना चाहिए

आप आसानी से संचालन की एक इंटरलेविंग लिख सकते हैं, जिसमें एक खाली कतार से शुरू, 2 enqueuers और 2 dequeuers बातचीत करते हैं और 1 enqueuer सोते रहेंगे।

यह गतिरोध समस्या के साथ तुलनात्मक रूप से एक समस्या है।


मेरे क्षमायाचना, xagyg इसे विस्तार से बताते हैं। समस्या का नाम "
लॉस्ट वेकअप

@ अभय बंसल: मुझे लगता है कि आप इस तथ्य को याद कर रहे हैं कि शर्त.वाइट () ताला जारी करती है और यह उस थ्रेड द्वारा फिर से जागृत हो जाती है जो उठता है।
NickV

4

notify()जाग जाएगा एक धागा, जबकि notifyAll()सभी जाग जाएगा। जहां तक ​​मुझे पता है कि कोई बीच का रास्ता नहीं है। लेकिन अगर आप सुनिश्चित नहीं हैं कि notify()आपके धागे का क्या होगा, उपयोग करें notifyAll()। हर बार आकर्षण की तरह काम करता है।


4

उपरोक्त सभी उत्तर सही हैं, जहां तक ​​मैं बता सकता हूं, इसलिए मैं आपको कुछ और बताने जा रहा हूं। उत्पादन कोड के लिए आपको वास्तव में java.util.concurrent की कक्षाओं का उपयोग करना चाहिए। वहाँ बहुत कम वे तुम्हारे लिए नहीं कर सकते हैं, जावा में संगामिति के क्षेत्र में।


4

notify()आपको इससे अधिक कुशल कोड लिखने देता है notifyAll()

कई समानांतर थ्रेड्स से निष्पादित कोड के निम्नलिखित टुकड़े पर विचार करें:

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

इसका उपयोग करके इसे और अधिक कुशल बनाया जा सकता है notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

यदि आपके पास बड़ी संख्या में धागे हैं, या यदि प्रतीक्षा लूप की स्थिति का मूल्यांकन करना महंगा है, तो notify()इससे काफी तेज होगा notifyAll()। उदाहरण के लिए, यदि आपके पास 1000 धागे हैं तो 999 धागे को पहले जागृत किया जाएगा और उसके बाद मूल्यांकन किया जाएगा notifyAll(), फिर 998, फिर 997, और इसी तरह। इसके विपरीत, notify()समाधान के साथ , केवल एक धागा जागृत होगा।

का प्रयोग करें notifyAll()जब आप जो धागा काम आगे क्या होगा चयन करने की आवश्यकता:

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

अंत में, यह समझना महत्वपूर्ण है कि के मामले में notifyAll(), synchronizedब्लॉक के अंदर कोड जो जागृत हो गए हैं, उन्हें क्रमिक रूप से निष्पादित किया जाएगा, सभी एक बार में नहीं। मान लीजिए कि उपरोक्त उदाहरण में तीन धागे हैं, और चौथा धागा कॉल है notifyAll()। सभी तीन धागे जागृत हो जाएंगे लेकिन केवल एक ही निष्पादन शुरू करेगा और whileलूप की स्थिति की जांच करेगा । यदि स्थिति है true, तो यह wait()फिर से कॉल करेगा , और उसके बाद ही दूसरा धागा निष्पादित करना शुरू कर देगा और इसकी whileलूप स्थिति की जांच करेगा , और इसी तरह।


4

यहाँ एक सरल व्याख्या है:

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

मान लीजिए कि थ्रेड A, B और C इस ऑब्जेक्ट पर प्रतीक्षा कर रहे थे, और थ्रेड A को मॉनिटर मिलता है। अंतर यह है कि क्या होता है एक बार मॉनिटर जारी करता है। यदि आपने अधिसूचित () का उपयोग किया है, तो B और C अभी भी प्रतीक्षा में अवरुद्ध हैं (): वे मॉनिटर पर प्रतीक्षा नहीं कर रहे हैं, वे अधिसूचित होने की प्रतीक्षा कर रहे हैं। जब A मॉनीटर जारी करता है, B और C तब भी वहां बैठे रहेंगे, एक सूचना () की प्रतीक्षा कर रहे हैं।

यदि आपने InformAll () का उपयोग किया है, तो B और C दोनों ने "सूचना के लिए प्रतीक्षा करें" स्थिति को उन्नत किया है और दोनों मॉनिटर का अधिग्रहण करने की प्रतीक्षा कर रहे हैं। जब A मॉनीटर जारी करता है, B या C उसे अधिग्रहित करेगा (यह मानते हुए कि कोई अन्य थ्रेड उस मॉनीटर के लिए प्रतिस्पर्धा नहीं कर रहे हैं) और निष्पादित करना शुरू करते हैं।


बहुत स्पष्ट व्याख्या। नोटिफ़िकेशन () के इस व्यवहार का परिणाम "मिस्ड सिग्नल" / "मिस्ड नोटिफिकेशन" के कारण हो सकता है, जो गतिरोध / अनुप्रयोग राज्य की प्रगति की स्थिति के लिए नहीं है। निर्माता, सी-उपभोक्ता पी 1, पी 2 और सी 2 सी 1 की प्रतीक्षा कर रहे हैं। C1 कॉल सूचना () है और एक निर्माता के लिए है, लेकिन C2 को जागृत किया जा सकता है और इसलिए P1 और P2 दोनों अधिसूचना से चूक गए और आगे स्पष्ट "अधिसूचना" (एक सूचना () कॉल) की प्रतीक्षा करेंगे।
user104309

4

यह उत्तर एक चित्रमय पुनर्लेखन है और xagyg द्वारा उत्कृष्ट उत्तर का सरलीकरण है , जिसमें eran द्वारा टिप्पणियां भी शामिल हैं ।

जब भी प्रत्येक उत्पाद एकल उपभोक्ता के लिए अभिप्रेत हो, तो InformAll का उपयोग क्यों करें?

उत्पादकों और उपभोक्ताओं पर विचार करें, निम्नानुसार सरलीकृत।

निर्माता:

while (!empty) {
   wait() // on full
}
put()
notify()

उपभोक्ता:

while (empty) {
   wait() // on empty
}
take()
notify()

2 उत्पादकों और 2 उपभोक्ताओं को मान लें, आकार का एक बफ़र साझा करना। 1. निम्न चित्र में एक गतिरोध के लिए एक परिदृश्य को दर्शाया गया है , जो सभी थ्रेड्स नोटिफ़ायर का उपयोग करने से बचा जाएगा ।

प्रत्येक सूचना को धागे के साथ जगाया जाता है।

सूचना के कारण गतिरोध


3

मैं इस बात का उल्लेख करना चाहूंगा कि जावा कंसेंबली इन प्रैक्टिस में क्या बताया गया है:

पहला बिंदु, क्या सूचित करें या NotifyAll?

It will be NotifyAll, and reason is that it will save from signall hijacking.

यदि दो थ्रेड A और B एक ही स्थिति कतार की अलग-अलग स्थिति पर प्रतीक्षा कर रहे हैं और सूचना को कॉल किया जाता है, तो यह JVM तक होता है, जो JVM थ्रेड को सूचित करेगा।

अब यदि थ्रेड ए और जेवीएम अधिसूचित थ्रेड बी के लिए सूचित किया गया था, तो थ्रेड बी उठेगा और देखेगा कि यह अधिसूचना उपयोगी नहीं है, इसलिए यह फिर से इंतजार करेगा। और थ्रेड ए को इस मिस्ड सिग्नल के बारे में कभी पता नहीं चलेगा और किसी ने इसे नोटिफाई किया है।

तो, CallAllAll इस समस्या को हल करेगा, लेकिन फिर से इसका प्रदर्शन प्रभाव होगा क्योंकि यह सभी थ्रेड को सूचित करेगा और सभी थ्रेड्स एक ही लॉक के लिए प्रतिस्पर्धा करेंगे और इसमें संदर्भ स्विच शामिल होगा और इसलिए CPU पर लोड होगा। लेकिन हमें प्रदर्शन की परवाह तभी करनी चाहिए जब वह सही ढंग से व्यवहार कर रहा हो, यदि यह व्यवहार स्वयं सही नहीं है तो प्रदर्शन का कोई फायदा नहीं है।

यह समस्या jdk 5 में प्रदान की गई स्पष्ट लॉकिंग लॉक की स्थिति वस्तु का उपयोग करके हल की जा सकती है, क्योंकि यह प्रत्येक स्थिति के लिए अलग प्रतीक्षा प्रदान करती है। यहां यह सही ढंग से व्यवहार करेगा और प्रदर्शन मुद्दा नहीं होगा क्योंकि यह सिग्नल को कॉल करेगा और सुनिश्चित करेगा कि केवल एक धागा उस स्थिति की प्रतीक्षा कर रहा है


3

notify()- ऑब्जेक्ट के प्रतीक्षा सेट से एक यादृच्छिक थ्रेड का चयन करता है और इसे BLOCKEDराज्य में रखता है । ऑब्जेक्ट के प्रतीक्षा सेट में शेष थ्रेड्स अभी भी WAITINGराज्य में हैं।

notifyAll()- ऑब्जेक्ट के प्रतीक्षा सेट से सभी थ्रेड्स को BLOCKEDस्थिति में ले जाता है। आपके द्वारा उपयोग किए जाने के बाद notifyAll(), साझा किए गए ऑब्जेक्ट के प्रतीक्षा सेट में कोई थ्रेड शेष नहीं हैं क्योंकि वे सभी अब BLOCKEDराज्य में हैं और WAITINGराज्य में नहीं हैं ।

BLOCKED- ताला अधिग्रहण के लिए अवरुद्ध। WAITING- नोटिफिकेशन का इंतजार (या ज्वाइनिंग पूरा होने के लिए ब्लॉक)।


3

प्रभावी जावा पर ब्लॉग से लिया :

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

तो, क्या मैं समझता हूँ (ऊपर उल्लिखित ब्लॉग से, टिप्पणी "यान टीएम" पर कर रहा है स्वीकार किए जाते हैं जवाब और जावा डॉक्स ):

  • सूचित करें (): जेवीएम इस वस्तु पर एक प्रतीक्षा सूत्र को जागृत करता है। थ्रेड चयन बिना निष्पक्षता के मनमाने ढंग से किया जाता है। तो एक ही धागे को बार-बार जगाया जा सकता है। इसलिए सिस्टम की स्थिति बदल जाती है लेकिन कोई वास्तविक प्रगति नहीं होती है। इस प्रकार एक लाइवलॉक का निर्माण
  • InformAll (): JVM सभी थ्रेड्स को जागृत करता है और फिर सभी थ्रेड्स इस ऑब्जेक्ट पर लॉक के लिए दौड़ते हैं। अब, CPU अनुसूचक एक थ्रेड का चयन करता है जो इस ऑब्जेक्ट पर लॉक का अधिग्रहण करता है। यह चयन प्रक्रिया JVM द्वारा चयन की तुलना में काफी बेहतर होगी। इस प्रकार, सुनिश्चित करना।

2

@Xagyg द्वारा पोस्ट किए गए कोड को देखें।

मान लीजिए दो अलग धागे दो अलग अलग स्थितियों के लिए इंतजार कर रहे हैं: पहला धागा इंतज़ार कर रहा है , और दूसरा धागा के लिए प्रतीक्षा कर रहा है
buf.size() != MAX_SIZEbuf.size() != 0

मान लीजिए कि कुछ बिंदु buf.size() 0 के बराबर नहीं है । जेवीएम के notify()बजाय कॉल करता है notifyAll(), और पहला धागा अधिसूचित होता है (दूसरा नहीं)।

पहला धागा जाग गया है, buf.size()जिसके लिए जाँच हो सकती है MAX_SIZE, और प्रतीक्षा करने के लिए वापस चला जाता है। दूसरा धागा जाग नहीं रहा है, इंतजार करना जारी रखता है और कॉल नहीं करता है get()


1

notify() पहला थ्रेड उठता है जिसे कहा जाता है wait()उसी वस्तु पर ।

notifyAll() जागने वाले सभी धागों को जगाता है wait()एक ही वस्तु पर ।

सर्वोच्च प्राथमिकता वाला धागा पहले चलेगा।


13
मामले में notify()यह बिल्कुल " पहला धागा " नहीं है।
भेश गुरुंग

6
आप यह अनुमान नहीं लगा सकते कि वीएम द्वारा किसे चुना जाएगा। केवल भगवान जानता है।
सर्गई शेवचेक

इस बात की कोई गारंटी नहीं है कि पहली (कोई निष्पक्षता) कौन होगी
इवान वोरोशिलिन

यह केवल पहले धागे को जगाएगा यदि ओएस गारंटी देता है, और यह संभावना है कि यह नहीं करता है। यह वास्तव में ओएस (और इसके अनुसूचक) को संदर्भित करता है कि किस थ्रेड को जगाना है।
पॉल स्टेलियन

1

अधिसूचित केवल एक थ्रेड को सूचित करेगा जो वेटिंग स्टेट में हैं, जबकि सभी को सूचित करेंगे वेटिंग स्टेट में सभी थ्रेड्स को सूचित करेंगे। अब सभी अधिसूचित थ्रेड्स और सभी ब्लॉक किए गए थ्रेड्स लॉक के लिए योग्य हैं, जिनमें से केवल एक को लॉक मिलेगा और अन्य सभी (वे लोग जो पहले प्रतीक्षा की स्थिति में हैं) अवरुद्ध अवस्था में होंगे।


1

ऊपर दिए गए उत्कृष्ट विस्तृत स्पष्टीकरणों को संक्षेप में प्रस्तुत करना, और सबसे सरल तरीके से, जिसके बारे में मैं सोच सकता हूं, यह जेवीएम निर्मित मॉनिटर की सीमाओं के कारण है, जो 1) संपूर्ण सिंक्रनाइज़ेशन इकाई (ब्लॉक या ऑब्जेक्ट) और 2) पर हासिल किया गया है प्रतीक्षा की जाने वाली / के बारे में विशिष्ट स्थिति के बारे में भेदभाव नहीं किया जाता है।

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

इसके विपरीत, InformAll () सभी वेटिंग थ्रेड्स को अंततः लॉक को फिर से प्राप्त करने और उनकी संबंधित स्थिति की जांच करने में सक्षम बनाता है, जिससे अंततः प्रगति की अनुमति मिलती है।

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


0

जब आप "ऑब्जेक्ट" के प्रतीक्षा () को कॉल करते हैं (ऑब्जेक्ट लॉक प्राप्त होने की उम्मीद करते हैं), तो यह उस ऑब्जेक्ट पर लॉक जारी करेगा और इस "ऑब्जेक्ट" पर लॉक करने के लिए अन्य थ्रेड्स की मदद करेगा, इस परिदृश्य में वहां होगा "संसाधन / ऑब्जेक्ट" के लिए 1 से अधिक थ्रेड प्रतीक्षा कर रहे हैं (अन्य थ्रेड्स पर भी उसी उपरोक्त ऑब्जेक्ट पर प्रतीक्षा जारी की है और जिस तरह से एक थ्रेड होगा जो संसाधन / ऑब्जेक्ट को भरता है और सूचित / सूचित करता है)।

यहां जब आप एक ही ऑब्जेक्ट (प्रक्रिया / कोड के समान / अन्य पक्ष से) का नोटिफिकेशन जारी करते हैं, तो यह एक अवरुद्ध और प्रतीक्षा कर रहा एक धागा जारी करेगा (सभी प्रतीक्षा धागे नहीं - यह जारी किया गया धागा JVM थ्रेड द्वारा उठाया जाएगा। शेड्यूलर और ऑब्जेक्ट पर सभी लॉक प्राप्त करने की प्रक्रिया नियमित रूप से समान है)।

यदि आपके पास केवल एक धागा है जो इस ऑब्जेक्ट पर साझा / काम कर रहा है, तो आपके प्रतीक्षा-अधिसूचित कार्यान्वयन में अकेले अधिसूचित () विधि का उपयोग करना ठीक है।

यदि आप ऐसी स्थिति में हैं जहां आपके व्यापार तर्क के आधार पर संसाधनों / ऑब्जेक्ट पर एक से अधिक थ्रेड पढ़े और लिखते हैं, तो आपको InformAll () के लिए जाना चाहिए

अब मैं देख रहा हूँ कि वास्तव में jvm कैसे प्रतीक्षा कर रहा है और प्रतीक्षा कर रहा है और जब हम किसी वस्तु पर सूचना () जारी करते हैं तो वह थ्रेड को तोड़ देता है ...


0

जबकि ऊपर कुछ ठोस जवाब हैं, मैंने जो भ्रम और गलतफहमी पढ़ी है, उससे हैरान हूं। यह शायद इस विचार को प्रमाणित करता है कि किसी को अपने टूटे हुए समवर्ती कोड को लिखने की कोशिश के बजाय java.util.concurrent का उपयोग करना चाहिए। प्रश्न पर वापस जाएं: संक्षेप में, आज के समय में सबसे अच्छी प्रथा AVOID अधिसूचित () के लिए सभी स्थितियों में खोई हुई वेकअप समस्या के कारण है। जो कोई भी इसे नहीं समझता है, उसे मिशन क्रिटिकल कंसीडर कोड लिखने की अनुमति नहीं दी जानी चाहिए। यदि आप हेरिंग समस्या के बारे में चिंतित हैं, तो एक समय में एक धागा जागने का एक सुरक्षित तरीका निम्न है: 1. वेटिंग थ्रेड्स के लिए एक स्पष्ट प्रतीक्षा कतार बनाएं; 2. कतार में धागा के प्रत्येक पूर्ववर्ती के लिए प्रतीक्षा करें; 3. प्रत्येक थ्रेड कॉल सूचित करें () जब किया। या आप Java.util.concurrent। * का उपयोग कर सकते हैं।


मेरे अनुभव में, प्रतीक्षा / सूचना का उपयोग अक्सर कतार तंत्र में किया जाता है जहां एक पंक्ति Runnableकी सामग्री को संसाधित करने वाला एक धागा ( कार्यान्वयन) होता है। wait()तो इस्तेमाल किया जब भी कतार खाली है है। और notify()जब सूचना जोड़ी जाती है तो उसे कहा जाता है। -> ऐसे मामले में केवल 1 थ्रेड होता है जो कभी भी कॉल करता है wait(), तो क्या यह उपयोग करने के लिए थोड़ा मूर्खतापूर्ण नहीं दिखता है notifyAll()अगर आपको पता है कि केवल 1 वेटिंग थ्रेड है।
bvdb

-2

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

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