2013-02-17 51 views
15

मैं प्रभावी जावा (द्वितीय संस्करण) के आइटम 71 के माध्यम से अपना रास्ता काम कर रहा हूं, "आलसी प्रारंभिक रूप से आलसी रूप से उपयोग करें"। यह इस कोड का उपयोग उदाहरण के क्षेत्रों (पृष्ठ 283) के आलसी आरंभीकरण के लिए दोबारा जांच मुहावरा के उपयोग का सुझाव देते हैं:'प्रभावी जावा' conundrum: इस समवर्ती कोड में अस्थिर क्यों आवश्यक है?

  1. अस्थिर क्यों है:

    private volatile FieldType field; 
    FieldType getField() { 
        FieldType result = field; 
        if (result == null) { //First check (no locking) 
         synchronized(this) { 
          result = field; 
          if (result == null) //Second check (with locking) 
           field = result = computeFieldValue(); 
         } 
        } 
        return result; 
    } 
    

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

  2. पाठ चलता है कि नहीं-आवश्यक स्थानीय चर, result, "यह सुनिश्चित करें कि field मामले ऐसे यह पहले से ही प्रारंभ कर रहा है में केवल एक बार पढ़ने के लिए है", जिससे प्रदर्शन में सुधार किया जाता है। यदि result हटा दिया गया था, तो field सामान्य मामले में कई बार पढ़ा जाएगा जहां यह पहले से ही प्रारंभ किया गया था?

+0

इस विषय पर कुछ पिछले प्रश्न हैं। कृपया एक नज़र डालें, और यदि कोई आपके प्रश्नों का उचित उत्तर देता है, तो इसे चिह्नित करने पर विचार करें। –

उत्तर

16

अस्थिर संशोधक मैदान पर क्यों आवश्यक यह देखते हुए कि आरंभीकरण एक तुल्यकालन ब्लॉक में जगह लेता है?

volatile वस्तुओं के निर्माण के आसपास निर्देशों की संभावित पुनरावृत्ति के कारण आवश्यक है। जावा मेमोरी मॉडल का कहना है कि रीयल-टाइम कंपाइलर के पास ऑब्जेक्ट कन्स्ट्रक्टर के बाहर फ़ील्ड प्रारंभिक स्थानांतरित करने के लिए निर्देशों को पुन: क्रमबद्ध करने का विकल्प होता है।

इसका मतलब है कि थ्रेड -1 को synchronized के अंदर प्रारंभ कर सकता है लेकिन वह थ्रेड -2 ऑब्जेक्ट को पूरी तरह से प्रारंभ नहीं कर सकता है। ऑब्जेक्ट को field पर असाइन करने से पहले किसी भी गैर-अंतिम फ़ील्ड को आरंभ करने की आवश्यकता नहीं है। volatile कीवर्ड यह सुनिश्चित करता है कि field इसे एक्सेस होने से पहले पूरी तरह आरंभ किया गया हो।

यह प्रसिद्ध "double check locking" bug का एक उदाहरण है।

यदि परिणाम हटा दिया गया था, तो आम मामले में फ़ील्ड को कई बार कैसे पढ़ा जाएगा जहां इसे पहले ही शुरू किया गया था?

जब भी आप volatile फ़ील्ड तक पहुंचते हैं, तो यह स्मृति-बाधा को पार करने का कारण बनता है। यह सामान्य क्षेत्र तक पहुंचने की तुलना में महंगा हो सकता है। एक स्थानीय चर में volatile फ़ील्ड की प्रतिलिपि बनाना एक सामान्य पैटर्न है यदि इसे उसी विधि में कई बार एक्सेस किया जाना है।

धागे के बीच बिना स्मृति-बाधाओं को एक वस्तु को साझा करने के खतरों के अधिक उदाहरण के लिए यहाँ मेरा उत्तर देखें:

About reference to object before object's constructor is finished

+0

यह कन्स्ट्रक्टर ऑपरेशन रीडरिंग के बारे में है। मैंने @pst बेहतर व्याख्या करने के लिए अपना उत्तर संपादित कर लिया है। जेआईटी कंपाइलर को कन्स्ट्रक्टर ऑपरेशंस को फिर से व्यवस्थित करने की अनुमति है _after_ इसे 'फ़ील्ड' को असाइन करता है। – Gray

+2

दूसरे शब्दों में, जैसा कि जॉन विंट एक और उत्तर में बताता है, गारंटीकृत दृश्यता का मतलब यह नहीं है कि पूर्ण निर्माण बनने से पहले आपकी नई वस्तु का संदर्भ आपके थ्रेड के लिए दृश्यमान हो सकता है (यानी यह 'फ़ील्ड' को गैर-शून्य के रूप में देखता है) दृश्यमान (यानी 'field.member' को नल के रूप में पढ़ा जा सकता है भले ही इसे कन्स्ट्रक्टर में प्रारंभ किया गया हो)। – Medo42

+2

@ Medo42 यह लगभग [सिंक्रनाइज़ेशन ऑर्डर] है (http://docs.oracle.com/javase/specs/jls/se5.0/html/memory.html#17.4.4), दृश्यता नहीं। असाइनमेंट की तत्काल दृश्यता * के साथ यह समस्या अभी भी मौजूद होगी।इस प्रकार यह होता है- पहले * ऑर्डरिंग * जो इसे टूटे हुए डीसीएल से काम करने वाले डीसीएल में बदलता है। –

7

यह एक काफी जटिल है, लेकिन यह अब संकलक से संबंधित है चीजों को पुनर्व्यवस्थित कर सकते हैं।
असल में Double Checked Locking पैटर्न जावा में काम नहीं करता है जब तक कि चर volatile नहीं है।

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

इस विषय पर this अन्य SO प्रश्न देखें।

+0

+1 क्योंकि समस्या, जैसा कि मैं इसे समझता हूं, को * रीडरिंग * के साथ करना है। –

+2

धन्यवाद @ bmorris591। अब से मैं अपने सभी बग्स 'विशेष परिणामों' को कॉल करने जा रहा हूं :) –

+0

यह या तो सही है या सही है? –

3

अच्छे प्रश्न।

फ़ील्ड पर अस्थिर संशोधक क्यों आवश्यक है कि प्रारंभिक सिंक्रनाइज़ किए गए ब्लॉक में होता है?

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

public class FieldType{ 
    Object obj = new Object(); 
    Object obj2 = new Object(); 
    public Object getObject(){return obj;} 
    public Object getObject2(){return obj2;} 
} 

यह getField() रिटर्न एक गैर-शून्य उदाहरण लेकिन यह है कि उदाहरण के getObj() और getObj2() तरीकों शून्य मान लौट सकते हैं संभव है। ऐसा इसलिए है क्योंकि सिंक्रनाइज़ेशन के बिना उन क्षेत्रों को लिखने से वस्तु के गठन के साथ दौड़ हो सकती है।

यह अस्थिर के साथ कैसे तय किया जाता है? सभी लिखते हैं कि एक अस्थिर लेखन से पहले होता है उस अस्थिर लेखन के बाद दिखाई देता है।

यदि परिणाम हटा दिया गया था, तो आम मामले में फ़ील्ड को कई बार कैसे पढ़ा जाएगा जहां इसे पहले ही शुरू किया गया था?

स्थानीय रूप से एक बार संग्रहीत करना और पूरे विधि में पढ़ना एक थ्रेड/प्रक्रिया स्थानीय स्टोर और सभी थ्रेड स्थानीय पढ़ता है। आप उन संबंधों में समयपूर्व अनुकूलन पर बहस कर सकते हैं लेकिन मुझे यह शैली पसंद है क्योंकि आप खुद को अजीब रीडरिंग समस्याओं में नहीं चलाएंगे जो ऐसा नहीं हो सकता है।

+0

आपके उत्तर जॉन के लिए धन्यवाद। –