जावा में प्रतीक्षा () और सूचना () का उपयोग करते हुए एक सरल परिदृश्य


181

क्या मुझे पूरा सरल परिदृश्य मिल सकता है अर्थात ट्यूटोरियल जो सुझाव देता है कि इसका उपयोग कैसे किया जाना चाहिए, विशेष रूप से क्यू के साथ?

जवाबों:


269

wait()और notify()तरीकों जब तक एक विशिष्ट स्थिति उत्पन्न होने पर ब्लॉक करने के लिए एक धागा अनुमति देने के लिए एक तंत्र प्रदान करने के लिए डिजाइन किए हैं। इसके लिए मुझे लगता है कि आप एक अवरुद्ध कतार कार्यान्वयन लिखना चाहते हैं, जहाँ आपके पास कुछ निश्चित आकार समर्थन तत्वों की दुकान है।

पहली चीज जो आपको करनी है वह यह है कि उन स्थितियों की पहचान करें जिन्हें आप प्रतीक्षा करने के तरीके चाहते हैं। इस स्थिति में, आप चाहते हैं कि put()जब तक स्टोर में खाली जगह take()न हो, तब तक ब्लॉक करने की विधि होगी, और आप तब तक ब्लॉक करने की विधि चाहते हैं जब तक कि लौटने के लिए कुछ तत्व न हो।

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while(queue.size() == capacity) {
            wait();
        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

जिस तरह से आपको प्रतीक्षा और सूचना तंत्र का उपयोग करना चाहिए, उस पर ध्यान देने के लिए कुछ चीजें हैं।

सबसे पहले, आप यह सुनिश्चित करने की जरूरत है कि किसी भी कॉल करने के लिए wait()या notify()कोड का एक सिंक्रनाइज़ क्षेत्र के भीतर (के साथ कर रहे हैं wait()और notify()एक ही वस्तु पर समन्वयित हो रहा कॉल)। इसका कारण (मानक थ्रेड सुरक्षा चिंताओं के अलावा) कुछ एक चूक संकेत के रूप में जाना जाता है।

इसका एक उदाहरण यह है कि एक धागा कॉल हो सकता है put()जब कतार पूरी होती है, तो यह स्थिति की जांच करता है, देखता है कि कतार पूरी है, हालांकि इससे पहले कि यह एक और धागा निर्धारित हो सके। यह दूसरा धागा तब take()कतार से एक तत्व है, और प्रतीक्षा के धागे को सूचित करता है कि कतार अब पूरी नहीं है। क्योंकि पहले थ्रेड ने पहले ही स्थिति की जांच कर ली है, यह wait()फिर से शेड्यूल होने के बाद कॉल करेगा , हालांकि यह प्रगति कर सकता है।

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

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


जैसा कि कुछ अन्य उत्तरों ने उल्लेख किया है, जावा 1.5 ने एक नया संगाम पुस्तकालय ( java.util.concurrentपैकेज में) पेश किया, जिसे प्रतीक्षा / सूचना तंत्र पर उच्च स्तर का अमूर्त प्रदान करने के लिए डिज़ाइन किया गया था। इन नई विशेषताओं का उपयोग करके, आप मूल उदाहरण को फिर से लिख सकते हैं जैसे:

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            while(queue.size() == capacity) {
                notFull.await();
            }

            queue.add(element);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while(queue.isEmpty()) {
                notEmpty.await();
            }

            T item = queue.remove();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

बेशक अगर आप वास्तव में एक अवरुद्ध कतार जरूरत है, तो आप के एक कार्यान्वयन का उपयोग करना चाहिए BlockingQueue इंटरफ़ेस।

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


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

1
@finnw जहां तक ​​मैं बता सकता हूं, आपके द्वारा स्पॉट की गई समस्या को InformAll () का उपयोग करके हल किया जा सकता है। क्या मैं सही हू?
क्लिंट ईस्टवुड

1
@ जारेड द्वारा यहां दिया गया उदाहरण बहुत अच्छा है लेकिन इसमें गंभीर गिरावट है। कोड में सभी विधियों को सिंक्रनाइज़ के रूप में चिह्नित किया गया है, लेकिन NO TWO SYNCHRONIZED METHODS CAN को एक ही समय में समाप्त किया जा सकता है, फिर तस्वीर में दूसरा धागा कैसे आता है।
शिवम अग्रवाल

10
@ Brut3Forc3 आपको प्रतीक्षा के javadoc को पढ़ने की आवश्यकता है (): यह कहता है: थ्रेड इस मॉनीटर का स्वामित्व जारी करता है । इसलिए, जैसे ही प्रतीक्षा () कहा जाता है, मॉनिटर जारी किया जाता है, और एक और धागा कतार के अन्य सिंक्रनाइज़ विधि को निष्पादित कर सकता है।
जेबी निज़ेट

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

148

एक कतार का उदाहरण नहीं, लेकिन बेहद सरल :)

class MyHouse {
    private boolean pizzaArrived = false;

    public void eatPizza(){
        synchronized(this){
            while(!pizzaArrived){
                wait();
            }
        }
        System.out.println("yumyum..");
    }

    public void pizzaGuy(){
        synchronized(this){
             this.pizzaArrived = true;
             notifyAll();
        }
    }
}

कुछ महत्वपूर्ण बिंदु:
1) कभी नहीं

 if(!pizzaArrived){
     wait();
 }

हमेशा (स्थिति) का उपयोग करें, क्योंकि

  • क) किसी के द्वारा अधिसूचित किए बिना प्रतीक्षा की स्थिति से धागे छिटक सकते हैं। (यहां तक ​​कि जब पिज्जा आदमी ने झंकार नहीं बजाई, तो कोई व्यक्ति पिज्जा खाने की कोशिश करेगा।)
  • बी) आपको सिंक्रनाइज़ लॉक प्राप्त करने के बाद फिर से स्थिति की जांच करनी चाहिए। मान लीजिए कि पिज्जा हमेशा के लिए नहीं रहता है। आप जागते हैं, पिज्जा के लिए लाइन-अप करते हैं, लेकिन यह हर किसी के लिए पर्याप्त नहीं है। यदि आप जाँच नहीं करते हैं, तो आप कागज खा सकते हैं! :) (शायद बेहतर उदाहरण होगा while(!pizzaExists){ wait(); }

2) आपको प्रतीक्षा / नौसिखिया को लागू करने से पहले ताला (सिंक्रनाइज़) रखना होगा। जागने से पहले थ्रेड्स को लॉक भी हासिल करना होता है।

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

4) नोटिफ़िकेशन () से सावधान रहें। जब तक आप जानते हैं कि आप क्या कर रहे हैं, तब तक InformAll () के साथ रहें।

5) अंतिम, लेकिन कम से कम, अभ्यास में जावा कॉनएरेबिलिटी नहीं पढ़ें !


1
क्या आप इस बात पर विस्तार से चर्चा कर सकते हैं कि "if (?? पिज़्ज़ा) {प्रतीक्षा ();}" का उपयोग क्यों नहीं करना चाहिए।
हर कोई

2
@ हर: कुछ स्पष्टीकरण जोड़ा गया। HTH।
एननो शियोजी

1
pizzaArrivedझंडे का उपयोग क्यों ? अगर झंडे को कॉल के बिना बदला जाता है notifyतो इसका कोई प्रभाव नहीं होगा। इसके अलावा सिर्फ उदाहरण के साथ waitऔर notifyकाम करता है।
पाब्लो फर्नांडीज

2
मुझे समझ में नहीं आता है - थ्रेड 1 ईटपाइज़र () विधि को निष्पादित करता है और शीर्ष सिंक्रनाइज़ ब्लॉक में प्रवेश करता है, और MyHouse क्लास पर सिंक्रनाइज़ होता है। कोई पिज्जा अभी तक नहीं आया है इसलिए इसका इंतजार है। अब थ्रेड 2 पिज्जाग्यू () विधि को कॉल करके पिज्जा को वितरित करने की कोशिश करता है; लेकिन जैसा कि 1 धागा पहले से ही लॉक का मालिक नहीं है और यह इसे नहीं दे रहा है (यह स्थायी रूप से प्रतीक्षा कर रहा है)। प्रभावी रूप से परिणाम एक गतिरोध है - थ्रेड 1 सूचना 2 (नोटरी) को निष्पादित करने के लिए थ्रेड 2 की प्रतीक्षा कर रहा है, जबकि थ्रेड 2 थ्रेड 1 के लिए प्रतीक्षा कर रहा है ताकि MyHouse वर्ग पर ताला छोड़ दिया जा सके ... यह क्या है कि मैं गायब हूँ यहाँ?
flamming_python

1
नहीं, जब एक चर को synchronizedकीवर्ड द्वारा संरक्षित किया जाता है , तो यह चर घोषित करने के लिए अतिरेक है volatile, और भ्रम से बचने के लिए इसे टालने की सिफारिश की जाती है @mrida
Enno Shioji

37

भले ही आपने wait()और notify()विशेष रूप से पूछा , मुझे लगता है कि यह उद्धरण अभी भी महत्वपूर्ण है:

जोश बलोच, प्रभावी जावा 2 संस्करण , आइटम 69: संगामिति उपयोगिताओं को प्राथमिकता दें waitऔर notify(उनके जोर दें):

उपयोग करने waitऔर notifyसही ढंग से करने की कठिनाई को देखते हुए , आपको इसके बजाय उच्च-स्तरीय संगामिति उपयोगिताओं का उपयोग करना चाहिए [...] का उपयोग करना waitऔर notifyसीधे "संगामिति विधानसभा भाषा" में प्रोग्रामिंग की तरह है, जैसा कि प्रदान की गई उच्च-स्तरीय भाषा की तुलना में है java.util.concurrentशायद ही कभी, उपयोग करने का कारण waitऔर notifyनए कोड में होता है


Java.util.concurrent पैकेज में प्रदान की गई BlockingQueueS लगातार नहीं है। जब कतार को स्थिर रखने की आवश्यकता हो तो हम क्या उपयोग कर सकते हैं? यानी यदि सिस्टम कतार में 20 वस्तुओं के साथ नीचे जाता है, तो मुझे सिस्टम के पुनरारंभ होने पर उपस्थित होने की आवश्यकता होती है। जैसा कि java.util.concurrent कतारों में सभी 'स्मृति में' प्रतीत होते हैं, केवल वही कोई तरीका है जो इनका उपयोग किया जा सकता है / जैसा कि हठ सक्षम करने के लिए कार्यान्वयन प्रदान करने के लिए / हैक किया गया है?
वोक्समैन

1
शायद समर्थन कतार प्रदान की जा सकती है? यानी हम एक कतार इंटरफ़ेस कार्यान्वयन प्रदान करेंगे जो लगातार है।
वोक्समैन

इस संदर्भ में यह उल्लेख करना बहुत अच्छा है कि आपको कभी भी notify()और wait()फिर से उपयोग करने की आवश्यकता नहीं होगी
चकलादार असफाक आरिफ

7

क्या आपने इस जावा ट्यूटोरियल को देखा है ?

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

कम से कम मैं जो करता हूं। मैं कंसीडर विशेषज्ञ नहीं हूं, इसलिए जहां भी संभव हो मैं हाथ से धागे को संभालने से दूर रहता हूं।


2

उदाहरण

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla bla bla
        }
     }
     public synchronized void threadCondWait(){
        while(myCondition){
           wait();//Comminucate with notify()
        }
     }

}
public class myAnotherThread extends Thread{
     @override
     public void run(){
        //Bla Bla bla
        notify();//Trigger wait() Next Step
     }

}

0

थ्रेडिंग में प्रतीक्षा () और अनौपचारिक () के लिए उदाहरण।

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

public class PrinterResource extends Thread{

//resource
public static List<String> arrayList = new ArrayList<String>();

public void addElement(String a){
    //System.out.println("Add element method "+this.getName());
    synchronized (arrayList) {
        arrayList.add(a);
        arrayList.notifyAll();
    }
}

public void removeElement(){
    //System.out.println("Remove element method  "+this.getName());
    synchronized (arrayList) {
        if(arrayList.size() == 0){
            try {
                arrayList.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            arrayList.remove(0);
        }
    }
}

public void run(){
    System.out.println("Thread name -- "+this.getName());
    if(!this.getName().equalsIgnoreCase("p4")){
        this.removeElement();
    }
    this.addElement("threads");

}

public static void main(String[] args) {
    PrinterResource p1 = new PrinterResource();
    p1.setName("p1");
    p1.start();

    PrinterResource p2 = new PrinterResource();
    p2.setName("p2");
    p2.start();


    PrinterResource p3 = new PrinterResource();
    p3.setName("p3");
    p3.start();


    PrinterResource p4 = new PrinterResource();
    p4.setName("p4");
    p4.start();     

    try{
        p1.join();
        p2.join();
        p3.join();
        p4.join();
    }catch(InterruptedException e){
        e.printStackTrace();
    }
    System.out.println("Final size of arraylist  "+arrayList.size());
   }
}

1
plz इस लाइन की दोबारा जाँच करें if(arrayList.size() == 0), मुझे लगता है कि यहाँ गलती हो सकती है।
विज्मन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.