2012-01-24 12 views
10

जब मैं Double Checked Locking मुहावरा के बारे में विकिपीडिया के लेख पढ़ रहा था, मैं उलझन में हूँ के बारे में यह कार्यान्वयन है:यह डबल-चेक लॉक एक अलग रैपर वर्ग के साथ लागू क्यों किया जाता है?

public class FinalWrapper<T> { 
    public final T value; 
    public FinalWrapper(T value) { 
     this.value = value; 
    } 
} 
public class Foo { 
    private FinalWrapper<Helper> helperWrapper = null; 

    public Helper getHelper() { 
     FinalWrapper<Helper> wrapper = helperWrapper; 

    if (wrapper == null) { 
     synchronized(this) { 
      if (helperWrapper == null) { 
       helperWrapper = new FinalWrapper<Helper>(new Helper()); 
      } 
      wrapper = helperWrapper; 
     } 
    } 
    return wrapper.value; 
    } 
} 

मैं बस समझ में नहीं आता कि हम क्यों आवरण बनाने के लिए की जरूरत है। क्या यह पर्याप्त नहीं है?

if (helperWrapper == null) { 
    synchronized(this) { 
     if (helperWrapper == null) { 
      helperWrapper = new FinalWrapper<Helper>(new Helper()); 
     } 
    } 
}  

यह है आवरण का उपयोग कर, क्योंकि आवरण ढेर पर संग्रहीत है और helperWrapper ढेर में संग्रहित है प्रारंभ में तेजी लाने सकता है, क्योंकि?

+0

@pst: मुझे लगता है कि वह अतिरिक्त 'रैपर' स्थानीय चर के बारे में पूछ रहा है जिसमें 'हेल्परवापर' की एक प्रति है। – casablanca

+0

@ कैसाब्लांका ओह, हम, उस कोण को पूरी तरह याद कर चुके हैं .... अब मैं उलझन में हूं, सवाल के बाकी शब्द के कारण। "मुझे समझ में नहीं आता कि हमें एक रैपर बनाने की जरूरत क्यों है।" :( –

+0

रास्ते से बढ़िया सवाल। एसओ! – Voo

उत्तर

1

क्या यह पर्याप्त नहीं है?

if (helperWrapper == null) { 
    synchronized(this) { 
     if (helperWrapper == null) { 
      helperWrapper = new FinalWrapper<Helper>(new Helper()); 
     } 
    } 
} 

कोई यह पर्याप्त नहीं है।

ऊपर, पहले helperWrapper == null के लिए जांचें थ्रेड सुरक्षित नहीं है। कुछ धागे "बहुत जल्दी" के लिए यह झूठी वापसी (गैर-शून्य उदाहरण देखकर), पूरी तरह से निर्मित हेल्परवापर ऑब्जेक्ट को इंगित करने की ओर इशारा करता है।

बहुत Wikipedia article आप का उल्लेख, इस मुद्दे को बताते हैं कदम-दर-कदम:

  1. थ्रेड एक नोटिस कि मान नहीं है:

    उदाहरण के लिए, घटनाओं के निम्न क्रम पर विचार प्रारंभ किया गया, इसलिए यह लॉक प्राप्त करता है और मान को प्रारंभ करने के लिए शुरू होता है।

  2. कुछ प्रोग्रामिंग भाषाओं के अर्थशास्त्र के कारण, संकलक द्वारा उत्पन्न कोड को पर आंशिक रूप से निर्मित ऑब्जेक्ट पर इंगित करने के लिए साझा चर को अद्यतन करने की अनुमति है, इससे पहले कि 0 ने प्रारंभ किया है।
  3. थ्रेड बी नोटिस करता है कि साझा चर प्रारंभ किया गया है (या ऐसा प्रतीत होता है), और इसका मान देता है। चूंकि थ्रेड बी का मानना ​​है कि मान पहले ही प्रारंभ हो चुका है, यह लॉक प्राप्त नहीं करता है। यदि बी का उपयोग करता है तो ए द्वारा किए गए सभी प्रारंभिकरणों को बी द्वारा देखा जाता है (या तो क्योंकि ए ने इसे प्रारंभ करना समाप्त नहीं किया है या क्योंकि में से कुछ ऑब्जेक्ट में प्रारंभिक मान मेमोरी बी का उपयोग नहीं किया गया है (कैश समेकन)), कार्यक्रम शायद दुर्घटनाग्रस्त हो जाएगा।

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

ऐसा इसलिए है क्योंकि रैपर का उपयोग प्रारंभिक गति को तेज कर सकता है क्योंकि रैपर ढेर और सहायक पर संग्रहीत होता है लपेटकर लपेटा जाता है?

नहीं, ऊपर कारण नहीं है।

कारण थ्रेड सुरक्षा है। फिर, जावा 1 का अर्थात्।5 और उच्चतर (जैसा कि जावा मेमोरी मॉडल में परिभाषित किया गया है) गारंटी देता है कि कोई भी धागा केवल सही ढंग से प्रारंभिक हेल्पर उदाहरण को रैपर से एक्सेस करने में सक्षम होगा क्योंकि यह कन्स्ट्रक्टर में प्रारंभिक अंतिम क्षेत्र है - JLS 17.5 Final Field Semantics देखें।

+1

में आपका स्वागत है मेरा मानना ​​है कि 'हेल्परवापर == नल' वाक्य के बाद पाठ में 'कुछ थ्रेड के लिए _false_ वापस आ सकता है'। – Cyberfox

+0

@ साइबरफ़ॉक्स आप सही हैं - इसे पकड़ने के लिए धन्यवाद! मैंने टाइपो – gnat

+0

को सही किया "यह कुछ धागे" बहुत जल्दी "के लिए झूठी वापसी (गैर-शून्य उदाहरण देखकर) हो सकता है, जो कि पूरी तरह से निर्मित हेल्पर का उदाहरण नहीं है।" <- कैसे "पूरी तरह से निर्मित नहीं हो सकता उदाहरण "यहां लीक हो जाएं? :( –

-1

मेरी समझ यह है कि wrapper का कारण यह है कि एक अपरिवर्तनीय वस्तु (सभी फ़ील्ड अंतिम हैं) पढ़ना एक परमाणु ऑपरेशन है।

यदि wrapper शून्य है, तो यह अभी तक एक अपरिवर्तनीय वस्तु नहीं है, और हमें synchronized ब्लॉक में गिरना होगा।

तो wrapper नहीं-रिक्त है तो हम कर रहे हैं गारंटी एक पूरी तरह से निर्माण किया value वस्तु है, इसलिए हम इसे वापस कर सकते हैं।

अपने कोड में, null चेक वास्तव में संदर्भित वस्तु को पढ़ने के बिना किया जा सकता है, और इसलिए ऑपरेशन की परमाणुता को ट्रिगर नहीं किया जा सकता है। अर्थात। ऑपरेशन के परिणाम को निर्माता के पास पास करने के लिए तैयारी में helperWrapper शुरू कर सकता था, लेकिन निर्माता को अभी तक नहीं बुलाया गया है।

अब मुझे लगता है यह होगा कि अपने उदाहरण में बाद में कोड पढ़ता था return helperWrapper.value; जो एक परमाणु संदर्भ पढ़ने को गति प्रदान करना चाहिए, की गारंटी देता है कि निर्माता को पूरा करता है, लेकिन यह पूरी तरह संभव ('कुछ प्रोग्रामिंग भाषाओं की अर्थ विज्ञान') है कि संकलक को ऑप्टिमाइज़ करने की इजाजत दी जाती है कि परमाणु पढ़ने को न करने के लिए, और इस प्रकार यह सटीक सही परिस्थितियों में एक अधूरा प्रारंभिक value ऑब्जेक्ट लौटाएगा।

स्थानीय चर का उपयोग करके बाधा निष्पादित करना और एक संदर्भ प्रतिलिपि परमाणु होने के लिए पढ़ने और लिखने को मजबूर करता है, और यह सुनिश्चित करता है कि कोड थ्रेड-सुरक्षित है।

मुझे विश्वास है कि मुख्य समझ यह है कि अपरिवर्तनीय संदर्भ पढ़ता है और लिखता परमाणु है, इसलिए किसी अन्य चर के लिए असाइनमेंट परमाणु है, लेकिन शून्य परीक्षण नहीं हो सकता है।

2

जैसा कि मैं कोड को समझता हूं स्थानीय wrapper वास्तव में आवश्यक नहीं है।

मेरा तर्क: कारण पहली बार रैपर चाल काम करता है क्योंकि जेएलएस गारंटी देता है कि संकलक किसी ऑब्जेक्ट का संदर्भ प्रकाशित नहीं कर सकता है इससे पहले कि उसके सभी अंतिम फ़ील्ड सही ढंग से प्रारंभ किए गए हों। इसलिए हमारे पास गारंटी है कि helperWrapper/wrapper शून्य हैं या helperWrapper/wrapper.value सही ढंग से प्रारंभ की गई वस्तु को इंगित करता है। कि अंतिम मान लिया जाये कि गारंटी अतिरिक्त स्थानीय या तो कुछ भी नहीं होता नहीं था: संकलक इसलिए स्थानीय बेमानी हो सकता है और नहीं करना चाहिए अपने दोनों wrapper और helperWrapper

लिए आंशिक रूप से बनाई गई चर आवंटित करने के लिए सही में पूरी तरह से किया जाएगा कोड की शुद्धता को प्रभावित करते हैं।

+0

, यह कहा गया "स्थानीय परिवर्तनीय रैपर को शुद्धता के लिए आवश्यक है।" तो मुझे अभी भी लगता है कि रैपर की जरूरत है। – Audrey

+0

@Audrey हाँ और जैसा कि हम सभी जानते हैं विकिपीडिया कभी गलत नहीं है। अब तक कोई भी किसी भी अच्छे तर्क का नाम नहीं दे सकता है कि स्थानीय क्यों आवश्यक होगा। Gnat के उत्तर में विकिपीडिया से उद्धृत समय सारिणी ** जावा मेमोरी मॉडल में अंतिम चर के साथ ** ** नहीं हो सकता है। – Voo

+0

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

1

बस शून्य जांच के लिए हेल्परवापर का उपयोग करके और जावा मेमोरी मॉडल के तहत पढ़ने की अनुमति के कारण वापसी विवरण विफल हो सकता है।

  1. पहले helperWrapper == null (सुरम्य पढ़ने) परीक्षण गलत का आकलन है, यानी helperWrapper अशक्त नहीं है:

    यहाँ उदाहरण परिदृश्य है।

  2. अंतिम पंक्ति, return helperWrapper.value (रेसी रीड) परिणामस्वरूप नलपोइंटर एक्सेप्शन में परिणाम, यानी।हेल्परवापर शून्य है

ऐसा कैसे हुआ? जावा मेमोरी मॉडल उन दो रासियों को फिर से व्यवस्थित करने की अनुमति देता है, क्योंकि पढ़ने से पहले कोई बाधा नहीं थी यानी "पहले से पहले" संबंध नहीं था। (String.hashCode example देखें)

ध्यान दें कि इससे पहले कि आप helperWrapper.value पढ़ सकें, आपको तुरंत helperWrapper संदर्भ स्वयं पढ़ना होगा। तो final द्वारा प्रदान की गई गारंटीएं कि हेल्परवापर पूरी तरह से तत्काल है, लागू नहीं होता है, क्योंकि वे केवल लागू होते हैं जब हेल्परवापर शून्य नहीं होता है।

+0

इसका मतलब है कि कार्रवाई सेट मदद रैपर करने के लिए रैपर अंतिम अर्थात् के कारण रीड रीडर को अस्वीकार करता है? – GhostFlying

+0

@GostFlying संख्या। जब हम 'रिटर्न wrapper.value' लिखते हैं तो हेल्परवापर के पढ़ने को फिर से नहीं बदला जा सकता है क्योंकि यह अब हेल्परवापर पढ़ नहीं रहा है। –

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

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