34

मैं हाल ही में जावा और इसके नुकसान में डबल चेक किए गए लॉकिंग पैटर्न पर चर्चा करने वाले एक लेख पर हुआ और अब मैं सोच रहा हूं कि उस पैटर्न का एक संस्करण जो मैं वर्षों से उपयोग कर रहा हूं अब किसी भी मुद्दे के अधीन है ।जावा डबल चेक लॉकिंग

मैंने विषय पर कई पदों और लेखों को देखा है और आंशिक रूप से निर्मित वस्तु का संदर्भ प्राप्त करने के साथ संभावित मुद्दों को समझ लिया है, और जहां तक ​​मैं कह सकता हूं, मैं नहीं सोचता मेरा कार्यान्वयन विषय है इन मुद्दों के लिए। क्या निम्नलिखित पैटर्न के साथ कोई समस्या है?

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

public class Test { 
    private static Test instance; 
    private static boolean initialized = false; 

    public static Test getInstance() { 
     if (!initialized) { 
      synchronized (Test.class) { 
       if (!initialized) { 
        instance = new Test(); 
        initialized = true; 
       } 
      } 
     } 
     return instance; 
    } 
} 
+0

मैं इसका उपयोग करता हूं। इसका कारण यह है कि प्रारंभिक = सत्य रेखा हिट होने से पहले पूरी तरह से तत्काल हो जाएगा और जावा में एक बूलियन मान असाइन करना एक परमाणु ऑपरेशन है (प्रत्येक चिपसेट पर मैंने कभी भी उपयोग किया है)। – Jono

+2

@ जोनो यह कंपाइलर और वीएम पर निर्भर करता है, अगर यह काम करता है या नहीं। कंपाइलर एक बाइटकोड उत्पन्न कर सकता है जो '! प्रारंभिक 'अभिव्यक्ति का मान _caching_ है, और इस प्रकार गायब हो जाता है यदि कोई अन्य थ्रेड' प्रारंभिक 'संपत्ति के मान को बदलता है। यदि एक ही कोड समानांतर चलाया जाता है, तो उदाहरण प्रारंभिकता को दो बार निष्पादित किया जा सकता है। इसके अलावा, जेआईटी कंपाइलर इस अभिव्यक्ति को एक गैर-अनुकूलित बाइटकोड से अनुकूलित कर सकता है। बस सोचें: वीएम को एक ही अभिव्यक्ति का मूल्यांकन करना है, तुरंत दूसरे के बाद। एक 'डुप्ली' ऑपरेशन पुनः मूल्यांकन को रोकता है, और जेएलएस के अनुसार एक मान्य कोड परिवर्तन है। – gaborsch

उत्तर

16

यही काम करेगा अगर initializedvolatile था। synchronized के साथ volatile के दिलचस्प प्रभाव इस संदर्भ के साथ वास्तव में बहुत कुछ नहीं हैं क्योंकि हम अन्य डेटा के बारे में क्या कह सकते हैं। instance फ़ील्ड की स्थापना और Test ऑब्जेक्ट को से पहले initialized पर लिखने के लिए मजबूर होना पड़ता है। शॉर्ट सर्किट के माध्यम से कैश किए गए मान का उपयोग करते समय, initializeinstance पढ़ने और संदर्भ के माध्यम से प्राप्त वस्तुओं से पहले होता है। अलग initialized ध्वज रखने में कोई महत्वपूर्ण अंतर नहीं है (इसके अलावा कोड में और भी जटिलता का कारण बनता है)।

(असुरक्षित प्रकाशन के लिए निर्माताओं में final क्षेत्रों के लिए नियमों को थोड़ा अलग हैं।)

लेकिन, आप शायद ही कभी इस मामले में बग को देखना चाहिए। पहली बार उपयोग करते समय परेशानी में पड़ने की संभावना न्यूनतम है, और यह एक गैर-बार-बार दौड़ है।

कोड अधिक जटिल है। आप इसे केवल लिख सकते हैं:

private static final Test instance = new Test(); 

public static Test getInstance() { 
    return instance; 
} 
+3

लेकिन एप्लिकेशन स्टार्टअप पर दुनिया बनाने से सावधान रहें। आलसी प्रारंभिकरण के उपयोग हैं। – rsp

+6

@rsp यह अभी भी आलसी है: जबकि आप कक्षा तक नहीं पहुंचते हैं, यह प्रारंभ नहीं होगा। लेकिन यह सच है कि, कुछ मामला है, हम चाहते हैं कि आलसी वास्तव में आखिरी पल में हो ... – KLE

+0

@ टॉम - आपके रिटर्न वैल्यू पर एक टाइपो है – McDowell

23

Double check locking is broken। प्रारंभिक होने के बाद से यह एक आदिम है, इसे काम करने के लिए अस्थिर होने की आवश्यकता नहीं हो सकती है, हालांकि कुछ भी शुरू होने से पहले गैर-सिंक्रोनिज्ड कोड के लिए प्रारंभिक रूप से देखा जाने वाला कुछ भी नहीं रोकता है।

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

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

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

+35

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

+1

@dtsazza, हाँ इसे अब अस्थिर के साथ तय किया जा सकता है, लेकिन ओपी के पास यह नहीं था, यह एक बुलियन के साथ इसे हल करने की कोशिश कर रहा था। – Yishai

11

डबल चेक लॉकिंग वास्तव में टूटा हुआ है, और समस्या का समाधान वास्तव में इस मुहावरे से कोड-वार लागू करने के लिए सरल है - बस एक स्थिर प्रारंभकर्ता का उपयोग करें।

public class Test { 
    private static final Test instance = createInstance(); 

    private static Test createInstance() { 
     // construction logic goes here... 
     return new Test(); 
    } 

    public static Test getInstance() { 
     return instance; 
    } 
} 

एक स्थिर प्रारंभकर्ता पहली बार है कि JVM वर्ग को लोड करता है निष्पादित करने के लिए गारंटी है, और वर्ग संदर्भ से पहले किसी भी धागा को लौट जा सकता है - यह स्वाभाविक threadsafe बना रही है।

+0

आपका उदाहरण गलत है क्योंकि * 'instance' * फ़ील्ड को * 'final' * के रूप में चिह्नित नहीं किया गया है, इसलिए, यह गारंटी नहीं है कि वहां कोई धागा नहीं है जो * 'null' * मान देख सकता है। –

+2

मुझे नहीं लगता कि यह http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html#44557 और http://java.sun.com/docs पर आधारित है /books/jls/second_edition/html/execution.doc.html#44630 - अंतिम संशोधक केवल उसी फ़ील्ड को प्रारंभ करने के क्रम को प्रभावित करता है। हालांकि इस क्षेत्र को उनके उपयोग में वैसे भी अंतिम होना चाहिए, इसलिए मैंने कोड को अद्यतन किए बिना अद्यतन किया है। –

0

आपको शायद java.util.concurrent.atomic में परमाणु डेटा प्रकारों का उपयोग करना चाहिए।

5

यही कारण है कि डबल चेक लॉकिंग टूटा हुआ है।

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

ऑर्डरिंग volatile द्वारा प्रदान की जाती है। volatile आदेश देने की गारंटी देता है, उदाहरण के लिए अस्थिर सिंगलटन स्थैतिक क्षेत्र की गारंटी लिखता है जो सिंगलटन ऑब्जेक्ट को लिखता है, अस्थिर स्थिर क्षेत्र में लिखने से पहले समाप्त हो जाएगा। यह दो वस्तुओं के सिंगलटन बनाने से नहीं रोकता है, यह सिंक्रनाइज़ द्वारा प्रदान किया जाता है।

कक्षा अंतिम स्थैतिक क्षेत्रों को अस्थिर होने की आवश्यकता नहीं है। जावा में, JVM इस समस्या का ख्याल रखता है।

मेरी पोस्ट, an answer to Singleton pattern and broken double checked locking in a real-world Java application देखें, जो सिंगलटन का एक उदाहरण दिखाता है जो डबल-चेक किए गए लॉकिंग के संबंध में चालाक दिखता है लेकिन टूटा हुआ है।

+0

यदि परिवर्तन अन्य धागे में देखा जाने की गारंटी नहीं है, तो इसका मतलब यह नहीं है कि विधि को सिंक्रनाइज़ करना भी टूट जाएगा? मैंने सोचा कि सुरक्षित होना चाहिए था। –

+0

@ बिलक एक ही ऑब्जेक्ट पर सिंक्रनाइज़ किए गए ब्लॉक में किसी भी पढ़ने के लिए सिंक्रनाइज़ किए गए ब्लॉक के अंदर किसी भी लिखने से पहले होता है। –

0

यदि "प्रारंभिक" सत्य है, तो "उदाहरण" पूरी तरह से प्रारंभ किया जाना चाहिए, जैसा कि 1 प्लस 1 बराबर 2 है :)। इसलिए, कोड सही है। उदाहरण केवल एक बार तत्काल होता है लेकिन फ़ंक्शन को दस लाख बार कहा जा सकता है, इसलिए यह एक लाख से कम के लिए सिंक्रनाइज़ेशन की जांच किए बिना प्रदर्शन में सुधार करता है।

0

अभी भी कुछ मामले हैं जब एक डबल चेक का उपयोग किया जा सकता है।

  1. सबसे पहले, यदि आपको वास्तव में सिंगलटन की आवश्यकता नहीं है, और डबल चेक का उपयोग केवल अधिक वस्तुओं को बनाने और प्रारंभ करने के लिए नहीं किया जाता है।
  2. कन्स्ट्रक्टर/प्रारंभिक ब्लॉक के अंत में final फ़ील्ड सेट है (जो सभी पहले प्रारंभिक फ़ील्ड को अन्य धागे द्वारा देखा जा सकता है)।
0

मैं डबल के बारे में जांच कर रहा है मुहावरा और ताला लगा मैं क्या समझ में आ से जाँच की है, अपने कोड एक आंशिक रूप से निर्माण उदाहरण पढ़ने की समस्या के लिए ले जा सकता है जब तक कि आपके टेस्ट वर्ग अपरिवर्तनीय है:

जावा मेमोरी मॉडल अपरिवर्तनीय वस्तुओं को साझा करने के लिए प्रारंभिक सुरक्षा की एक विशेष गारंटी प्रदान करता है।

ऑब्जेक्ट संदर्भ प्रकाशित करने के लिए सिंक्रनाइज़ेशन का उपयोग नहीं होने पर भी उन्हें सुरक्षित रूप से एक्सेस किया जा सकता है।

तो उस मामले में

(बहुत उचित पुस्तक अभ्यास में जावा संगामिति से कोटेशन), डबल ताला लगा मुहावरा काम करेगा जाँच की।

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

बूलियन वैरिएबल समस्या से बचने के लिए कुछ भी नहीं जोड़ता है, क्योंकि टेस्ट क्लास शुरू होने से पहले इसे सत्य पर सेट किया जा सकता है (सिंक्रनाइज़ किए गए कीवर्ड पूरी तरह से पुन: व्यवस्थित होने से नहीं बचते हैं, कुछ सेंकेंस ऑर्डर बदल सकते हैं)। इसकी गारंटी के लिए जावा मेमोरी मॉडल में नियम से पहले ऐसा नहीं होता है।

और बूलियन अस्थिर कुछ भी या तो जोड़ नहीं होगा बनाने, क्योंकि 32 बिट चर जावा में atomically बनाई गई हैं। डबल चेक लॉकिंग मुहावरे उनके साथ भी काम करेगा।

जावा 5 के बाद से, आप उस समस्या को अस्थिर रूप में उदाहरण चर घोषित ठीक कर सकते हैं।

आप this very interesting article में डबल जाँच मुहावरा के बारे में अधिक पढ़ सकते हैं।

अंत में, कुछ सिफारिशों मैं पढ़ा है:

  • पर विचार करें यदि आप सिंगलटन पैटर्न का उपयोग करना चाहिए। इसे कई लोगों द्वारा एक विरोधी पैटर्न माना जाता है। जहां संभव हो निर्भरता इंजेक्शन पसंद किया जाता है। this देखें।

  • सावधानी से विचार करें, तो डबल जाँच ताला लगा अनुकूलन कार्यान्वित करने से पहले वास्तव में आवश्यक है, क्योंकि ज्यादातर मामलों में, कि प्रयास के लायक नहीं होगा। साथ ही, स्थैतिक क्षेत्र में टेस्ट क्लास का निर्माण करने पर विचार करें, क्योंकि आलसी लोडिंग केवल तब उपयोगी होती है जब कक्षा बनाने में बहुत सारे संसाधन होते हैं और ज्यादातर बार, यह मामला नहीं है।

आप अभी भी इस अनुकूलन प्रदर्शन करने की जरूरत है, इस link जो आप क्या प्रयास कर रहे हैं करने के लिए एक समान प्रभाव प्राप्त करने के लिए कुछ विकल्प प्रदान करता है की जाँच करें।

0

DCL समस्या टूट गया है, भले ही यह कई VMs पर काम करता है लगता है। http://www.javaworld.com/article/2075306/java-concurrency/can-double-checked-locking-be-fixed-.html पर समस्या के बारे में एक अच्छा लेखन है।

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

इस समस्या को ठीक से हल करने के लिए एक ही रास्ता एक तुल्यकालन ब्लॉक के अंदर आलसी आरंभीकरण (यह बेसब्री से करते हैं) या एक चेक करने से बचना है। बूलियन initialized का उपयोग संदर्भ पर एक शून्य जांच के बराबर है। एक दूसरा धागा initialized सच हो सकता है लेकिन instance अभी भी शून्य या आंशिक रूप से प्रारंभ हो सकता है।

0

की दोबारा जांच कर लॉकिंग विरोधी पैटर्न है।

आलसी प्रारंभिक धारक वर्ग वह पैटर्न है जिसे आप देखना चाहते हैं।

इतने सारे उत्तरों के बावजूद, मुझे लगा कि मुझे जवाब देना चाहिए क्योंकि अभी भी एक आसान जवाब नहीं है जो कहता है कि क्यों कई संदर्भों में डीसीएल टूट गया है, यह अनावश्यक क्यों है और इसके बजाय आपको क्या करना चाहिए। तो मैं Goetz: Java Concurrency In Practice से एक उद्धरण का उपयोग करूंगा जो मेरे लिए जावा मेमोरी मॉडल पर अपने अंतिम अध्याय में सबसे शानदार स्पष्टीकरण प्रदान करता है।

यह चर के बारे में सुरक्षित प्रकाशन है: (इस में

DCL साथ वास्तविक समस्या धारणा है कि सबसे बुरी बात यह है कि हो सकता है जब तुल्यकालन के बिना एक साझा ऑब्जेक्ट संदर्भ पढ़ने ग़लती से एक बासी मूल्य देखने के लिए है मामला, शून्य); उस स्थिति में डीसीएल मुहावरे लॉक के साथ फिर से प्रयास करके इस जोखिम के लिए क्षतिपूर्ति करता है। लेकिन सबसे बुरी स्थिति वास्तव में काफी खराब है-संदर्भ के वर्तमान मूल्य को देखना संभव है लेकिन ऑब्जेक्ट के राज्य के लिए मूल्यवान मान, जिसका अर्थ यह है कि ऑब्जेक्ट को अमान्य या गलत स्थिति में देखा जा सकता है।

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

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

लिस्टिंग 16.6। आलसी प्रारंभिक धारक वर्ग Idiom।

public class ResourceFactory 
    private static class ResourceHolder { 
     public static Resource resource = new Resource(); 
    } 

    public static Resource getResource() { 
     return ResourceHolder.resource; 
    } 
} 

जिस तरह से यह करना है कि।

0

पहले, एकमात्र के लिए आप एक Enum उपयोग कर सकते हैं, इस सवाल में बताई गई विधि Implementing Singleton with an Enum (in Java)

दूसरा, जावा 1.5 के बाद से, आप एक अस्थिर चर डबल जाँच की लॉकिंग के साथ, के रूप में इस लेख के अंत में विस्तार से बताया उपयोग कर सकते हैं: https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

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