2011-08-30 27 views
9

निम्नलिखित जावा कोड को देखते हुए:जावा प्रतीक्षा()/शामिल हों(): यह डेडलॉक क्यों नहीं करता है?

public class Test { 

    static private class MyThread extends Thread { 
     private boolean mustShutdown = false; 

     @Override 
     public synchronized void run() { 
      // loop and do nothing, just wait until we must shut down 
      while (!mustShutdown) { 
       try { 
        wait(); 
       } catch (InterruptedException e) { 
        System.out.println("Exception on wait()"); 
       } 
      } 
     } 

     public synchronized void shutdown() throws InterruptedException { 
      // set flag for termination, notify the thread and wait for it to die 
      mustShutdown = true; 
      notify(); 
      join(); // lock still being held here, due to 'synchronized' 
     } 
    } 

    public static void main(String[] args) { 
     MyThread mt = new MyThread(); 
     mt.start(); 

     try { 
      Thread.sleep(1000); 
      mt.shutdown(); 
     } catch (InterruptedException e) { 
      System.out.println("Exception in main()"); 
     } 
    } 
} 

इस रनिंग एक सेकंड के लिए प्रतीक्षा करें और फिर ठीक से बाहर निकल जाएगा। लेकिन यह मेरे लिए अप्रत्याशित है, मुझे उम्मीद है कि यहां एक मृत-ताला होना होगा।

मेरा तर्क इस प्रकार है: नव निर्मित MyThread रन() को निष्पादित करेगा, जिसे 'सिंक्रनाइज़' के रूप में घोषित किया जाता है, ताकि वह प्रतीक्षा() को कॉल कर सके और सुरक्षित रूप से 'mustShutdown' पढ़ सके; उस प्रतीक्षा() कॉल के दौरान, लॉक जारी किया जाता है और लौटने पर फिर से अधिग्रहण किया जाता है, जैसा कि प्रतीक्षा() के दस्तावेज़ीकरण में वर्णित है। एक सेकंड के बाद, मुख्य धागा शटडाउन() को निष्पादित करता है, जिसे फिर से सिंक्रनाइज़ किया जाता है क्योंकि उसी थ्रेडशॉटडाउन को उसी समय तक एक्सेस नहीं किया जा सकता है क्योंकि इसे अन्य थ्रेड द्वारा पढ़ा जा रहा है। इसके बाद यह अधिसूचना() के माध्यम से अन्य धागे को जगाता है और इसमें शामिल होने के माध्यम से पूरा होने की प्रतीक्षा करता है।

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

+0

इस तरह के साइड इफेक्ट्स की वजह से थ्रेड सीधे फैला हुआ है। आपको एक रननेबल को लागू करना चाहिए जिसे आप थ्रेड के साथ लपेटते हैं। –

उत्तर

7

में शामिल होने के() विधि आंतरिक प्रतीक्षा() जो (थ्रेड वस्तु का) ताला रिहा में परिणाम होगा कहते हैं।

public final synchronized void join(long millis) 
    throws InterruptedException { 
    .... 
    if (millis == 0) { 
     while (isAlive()) { 
     wait(0); //ends up releasing lock 
     } 
    } 
    .... 
} 

कारण है कि अपने कोड यह देखता है और सामान्य रूप में नहीं देखा:: कारण है कि अपने कोड में देखते और सामान्य रूप में नहीं मनाया नहीं है,

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

+1

जो वास्तव में इसे समझाता है। लेकिन जैसा कि पालो एबरमैन बताते हैं, दस्तावेज़ीकरण इस बारे में कुछ भी नहीं कहता है। क्या मैं लॉक जारी करने() लॉक जारी करने पर भरोसा कर सकता हूं? – jlh

+0

बिल्कुल हां। चूंकि मैंने युवावस्था को मारा क्योंकि कोड अपरिवर्तित है। परवाह नहीं। –

+0

बहुत अच्छा! बहुत सराहना की, धन्यवाद! – jlh

1

Thread.join का कार्यान्वयन प्रतीक्षा का उपयोग करता है, जो इसके लॉक को जाने देता है, यही कारण है कि यह अन्य थ्रेड को लॉक प्राप्त करने से नहीं रोकता है।

एक नया धागा MyThread रन विधि को क्रियान्वित करने में मुख्य विधि परिणामों में MyThread धागा शुरू:

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

नया धागा फिर प्रतीक्षा विधि दर्ज कर सकता है और इसके लॉक को छोड़ सकता है। इस बिंदु पर नया धागा निष्क्रिय हो जाता है, यह तब तक लॉक प्राप्त करने की कोशिश नहीं करेगा जब तक यह जागृत न हो जाए। थ्रेड अभी तक प्रतीक्षा विधि से वापस नहीं करता है।

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

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

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

/** 
* Waits at most <code>millis</code> milliseconds for this thread to 
* die. A timeout of <code>0</code> means to wait forever. 
* 
* @param  millis the time to wait in milliseconds. 
* @exception InterruptedException if any thread has interrupted 
*    the current thread. The <i>interrupted status</i> of the 
*    current thread is cleared when this exception is thrown. 
*/ 
public final synchronized void join(long millis) 
throws InterruptedException { 
long base = System.currentTimeMillis(); 
long now = 0; 

if (millis < 0) { 
     throw new IllegalArgumentException("timeout value is negative"); 
} 

if (millis == 0) { 
    while (isAlive()) { 
    wait(0); 
    } 
} else { 
    while (isAlive()) { 
    long delay = millis - now; 
    if (delay <= 0) { 
     break; 
    } 
    wait(delay); 
    now = System.currentTimeMillis() - base; 
    } 
} 
} 
0

अन्य उत्तरों के पूरक के लिए: मुझे join() का कोई उल्लेख नहीं है जो एपीआई-दस्तावेज़ में किसी भी ताले को जारी करता है, इसलिए यह व्यवहार वास्तव में कार्यान्वयन-विशिष्ट है।

इस से जानें:

  • Thread उपवर्ग नहीं है, बजाय एक Runnable कार्यान्वयन अपने धागा वस्तु के लिए पारित का उपयोग करें।
  • उन वस्तुओं पर सिंक्रनाइज़/प्रतीक्षा/सूचित न करें जिन्हें आप "स्वयं" नहीं करते हैं, उदा। जहां आप नहीं जानते कि कौन और सिंक्रनाइज़/प्रतीक्षा/अधिसूचित कर सकता है।
+0

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

+0

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

संबंधित मुद्दे