JSR 133 (जावा मेमोरी मॉडल) FAQ (अनुवाद)

शुभ दोपहर
जावा कोर्स में मल्टीकोर प्रोग्रामिंग के हिस्से के रूप में, मैं जावा में मल्टीथ्रेडिंग पर क्लासिक लेखों के अनुवादों की एक श्रृंखला कर रहा हूं। मल्टीथ्रेडिंग का कोई भी अध्ययन मेमोरी मॉडल में जावा (न्यू जेएमएम) को शुरू करने से शुरू होना चाहिए, मॉडल के लेखकों का मुख्य स्रोत "जावा मेमोरी मॉडल" होम पेज है , जहां शुरुआत के लिए इसे जेएसआर 133 (जावा मेमोरी मॉडल) से परिचित होने का सुझाव दिया गया है। इसलिए इस लेख के अनुवाद के साथ, मैंने श्रृंखला शुरू करने का फैसला किया।
मैंने अपने आप को "अपने दम पर" कुछ सम्मिलन की अनुमति दी, जो मेरी राय में, स्थिति को स्पष्ट करते हैं।
मैं जावा और मल्टीथ्रेडिंग का विशेषज्ञ हूं, न कि एक दार्शनिक या अनुवादक, इसलिए मैं अनुवाद करने के लिए कुछ स्वतंत्रताओं या सुधारों को स्वीकार करता हूं। यदि आप सबसे अच्छा विकल्प प्रस्तावित करते हैं - तो मुझे सुधार करने में खुशी होगी।
यह लेख "व्याख्यान # 5.2: जेएमएम (अस्थिर, अंतिम, सिंक्रनाइज़)" व्याख्यान के लिए एक प्रशिक्षण सामग्री के रूप में भी उपयुक्त है।

मैं ऑनलाइन शिक्षा प्लेटफॉर्म udemy.com (कौरसेरा / एडएक्स के समान) पर जावा डेवलपर्स कोर्स के लिए स्काला भी सिखाता हूं।

अच्छा और हाँ, मेरे साथ अध्ययन करने आओ!


JSR 133 (जावा मेमोरी मॉडल) FAQ


जेरेमी मैनसन और ब्रायन गोएत्ज़, फरवरी 2004

सामग्री:
मेमोरी मॉडल क्या है, आखिरकार क्या है?
क्या अन्य भाषाएँ, जैसे कि C ++, में एक मेमोरी मॉडल है?
JSR 133 क्या है?
"रिडरिंग" से क्या अभिप्राय है?
पुराने मेमोरी मॉडल में क्या गलत था?
"गलत तरीके से सिंक्रनाइज़" से आपका क्या मतलब है?
सिंक्रनाइज़ेशन क्या करता है?
ऐसा कैसे हो सकता है कि अंतिम क्षेत्र मानों को बदल दें?
नए JMM के तहत अंतिम फ़ील्ड कैसे काम करते हैं?
अस्थिर क्या करता है?
क्या नए डबल-चेकिंग लॉकिंग मेमोरी मॉडल ने समस्या को हल किया?
अगर मैं एक वर्चुअल मशीन लिखूं तो क्या होगा?
मुझे क्यों परेशान करना चाहिए?


मेमोरी मॉडल क्या है, आखिरकार क्या है?


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

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

बाधाओं की कम आवश्यकता के कारण, मजबूत मेमोरी मॉडल के लिए कार्यक्रम लिखना कभी-कभी आसान होता है। हालांकि, सबसे मजबूत मेमोरी मॉडल पर भी, बाधाएं अक्सर आवश्यक होती हैं; अक्सर, उनका स्थान अंतर्ज्ञान के विपरीत होता है। प्रोसेसर डिजाइन में हालिया रुझान कमजोर मेमोरी मॉडल को प्रोत्साहित कर रहे हैं क्योंकि वे कैश की निरंतरता के लिए जो रियायतें देते हैं, वे कई प्रोसेसर और बड़ी मात्रा में मेमोरी में वृद्धि हुई स्केलेबिलिटी प्रदान करते हैं।

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

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

इसका एक सरल उदाहरण निम्नलिखित कोड स्निपेट में देखा जा सकता है:
class Reordering { int x = 0, y = 0; public void writer() { x = 1; y = 2; } public void reader() { int r1 = y; int r2 = x; } } 


मान लेते हैं कि इस कोड को एक ही समय में दो थ्रेड्स में निष्पादित किया जाता है और 'y' मान को पढ़ने के लिए 2 वापस आता है। चूँकि यह रिकॉर्ड 'x' में लिखने के बाद स्थित है, प्रोग्रामर मान सकता है कि 'x' को पढ़ने से मान 1 वापस आ जाना चाहिए। हालाँकि, रिकॉर्ड 'x' और 'y' में पुन: क्रमबद्ध किया गया हो सकता है। यदि ऐसा होता, तो 'y' को लिखना हो सकता था, फिर दोनों चर को पढ़ना, और उसके बाद ही 'x' को लिखना। परिणाम यह होगा कि r1 का मान 2 है और r2 का मान 0 है।
अनुवादक टिप्पणी
यह माना जाता है कि एक ही वस्तु के लिए, पाठक () विधि और लेखक () विधि "लगभग एक साथ" अलग-अलग धागे से कहते हैं।

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

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

क्या अन्य भाषाएँ, जैसे कि C ++, में एक मेमोरी मॉडल है?


अधिकांश अन्य प्रोग्रामिंग भाषाएं, जैसे कि C और C ++, को मल्टीथ्रेडिंग के प्रत्यक्ष समर्थन के साथ विकसित नहीं किया गया है। संकलक और प्रोसेसर में होने वाले विभिन्न प्रकार के पुनरावृत्ति के खिलाफ ये भाषाएं जो सुरक्षात्मक उपाय पेश करती हैं, वे काफी हद तक संकलक द्वारा उपयोग किए जाने वाले समानांतर पुस्तकालयों (जैसे pthreads) द्वारा उपयोग की जाने वाली गारंटियों पर निर्भर करती हैं और जिस मंच पर कोड चलता है।
अनुवादक टिप्पणी
जावा ने मेमोरी मॉडल को भाषा विनिर्देश में शुरू करने के लिए प्रवृत्ति निर्धारित की, और नवीनतम सी ++ 11 मानक में पहले से ही एक मेमोरी मॉडल (अध्याय 1.7, "सी ++ मेमोरी मॉडल") है । ऐसा लगता है कि C11 के पास पहले से ही है।


JSR 133 क्या है?


1997 से, जावा मेमोरी मॉडल में कई गंभीर खामियों की खोज की गई है, जिसे भाषा विनिर्देश के अध्याय 17 में परिभाषित किया गया है। इन कमियों ने चौंकाने वाला व्यवहार करने की अनुमति दी (उदाहरण के लिए, अंतिम फ़ील्ड मान को बदलने की अनुमति दी गई थी) और संकलक को विशिष्ट अनुकूलन का उपयोग करने से रोका।
अनुवादक टिप्पणी
पुराने मेमोरी मॉडल (ओल्ड जेएमएम, जावा 5 से पहले) को "अध्याय 17. थ्रेड्स और लॉक्स" भाषा विनिर्देश के अध्याय 17 में विशेष रूप से वर्णित किया गया था।
नया मेमोरी मॉडल (जावा 5 से शुरू होने वाला नया JMM) भाषा विनिर्देश के अध्याय 17 "अध्याय 17. सूत्र और ताले" दोनों में वर्णित है और एक अलग दस्तावेज़ (JSR-133) में विस्तारित किया गया है।
नया जाम
पुरानी झामुमो:


जावा मेमोरी मॉडल एक महत्वाकांक्षी परियोजना थी; पहली बार, एक प्रोग्रामिंग भाषा विनिर्देश ने एक मेमोरी मॉडल को शामिल करने का प्रयास किया जो विभिन्न प्रोसेसर आर्किटेक्चर के बीच संगामिति के लिए निरंतर शब्दार्थ प्रदान कर सकता है। दुर्भाग्य से, एक मेमोरी मॉडल को परिभाषित करना जो सुसंगत और सहज दोनों है, उम्मीद से अधिक कठिन साबित हुआ है। JSR 133 जावा के लिए एक नया मेमोरी मॉडल परिभाषित करता है जो पिछले मॉडल की खामियों को ठीक करता है। ऐसा करने के लिए, अंतिम और वाष्पशील के शब्दार्थों को बदल दिया गया है।

शब्दार्थ का पूरा विवरण http://www.cs.umd.edu/users/pugh/java/memoryModel पर उपलब्ध है, लेकिन औपचारिक विवरण डरपोक के लिए नहीं है। यह आश्चर्य की बात है और जब आपको पता चलता है कि वास्तव में सिंक्रोनाइज़ेशन जैसी सरल अवधारणा कितनी जटिल है। सौभाग्य से, आपको औपचारिक शब्दार्थ के सभी विवरणों को समझने की आवश्यकता नहीं है - जेएसआर 133 का लक्ष्य एक नियम सेट बनाना था जो सहज, सिंक्रनाइज़ और अंतिम कार्य की सहज समझ प्रदान करता है।
अनुवादक टिप्पणी
लेखक जावा मेमोरी मॉडल के "होम पेज" के लिए एक लिंक देता है - इंटरनेट पर जानकारी का मुख्य स्रोत।

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

JSR 133 के उद्देश्यों में शामिल हैं:


"रिडरिंग" से क्या अभिप्राय है?


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

उदाहरण के लिए, यदि कोई स्ट्रीम 'a' फ़ील्ड में लिखती है, और फिर फ़ील्ड 'b' में और 'b' का मान 'a' के मान पर निर्भर नहीं करता है, तो संकलक इन परिचालनों के क्रम को बदलने के लिए स्वतंत्र है, और कैश को 'b' को फ्लश करने का अधिकार है। 'a' से पहले RAM में। पुनर्संरचना के कई संभावित स्रोत हैं, जैसे संकलक, जेआईटी, और कैश।

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

ज्यादातर समय, थ्रेड्स यह विचार नहीं करते हैं कि अन्य धागे क्या करते हैं। लेकिन जब उन्हें इसकी आवश्यकता होती है, तो सिंक्रनाइज़ेशन खेलने में आता है।

पुराने मेमोरी मॉडल में क्या गलत था?


पुराने मेमोरी मॉडल के साथ कई गंभीर समस्याएं थीं। यह समझना मुश्किल था और इसलिए अक्सर उल्लंघन होता था। उदाहरण के लिए, कई मामलों में पुराने मॉडल ने कई प्रकार के पुनरावृत्ति की अनुमति नहीं दी थी जो प्रत्येक जेवीएम में लागू किए गए थे। पुराने मॉडल के अर्थ के साथ यह भ्रम इस तथ्य को जन्म देता है कि उन्हें JSR-133 बनाने के लिए मजबूर किया गया था।

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

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

अंत में, यदि उनके प्रोग्राम गलत तरीके से सिंक्रोनाइज़ किए जाते हैं तो प्रोग्रामर्स की धारणाएं अक्सर गलत होती हैं। जेएसआर -133 का एक लक्ष्य इस तथ्य पर ध्यान देना है।

"गलत तरीके से सिंक्रनाइज़" से आपका क्या मतलब है?


गलत तरीके से सिंक्रनाइज़ किए गए कोड के तहत, अलग-अलग लोगों का मतलब अलग-अलग चीजें हैं। जब हम जावा मेमोरी मॉडल के संदर्भ में गलत तरीके से सिंक्रनाइज़ कोड के बारे में बात करते हैं, तो हमारा मतलब किसी भी कोड से है:
  1. एक सूत्र में एक चर रिकॉर्ड है,
  2. वहाँ एक और चर के द्वारा एक ही चर के पढ़ने है और
  3. पढ़ना और लिखना सिंक्रनाइज़ेशन द्वारा आदेश नहीं दिया गया है

जब ऐसा होता है, तो हम कहते हैं कि इस चर पर एक डेटा दौड़ है। स्ट्रीम रेसिंग प्रोग्राम - गलत तरीके से सिंक्रनाइज़ किए गए प्रोग्राम।
अनुवादक टिप्पणी
यह समझना चाहिए कि गलत तरीके से सिंक्रनाइज़ किए गए प्रोग्राम एक पूर्ण बुराई नहीं हैं। यद्यपि उनका व्यवहार गैर-निर्धारक है, सभी संभावित परिदृश्यों को जेएसआर -133 में पूरी तरह से वर्णित किया गया है। ये व्यवहार प्रायः अचूक होते हैं। प्रतिबद्धता प्रोटोकॉल और करणीय छोरों के रूप में इस तरह की नई अवधारणाओं पर सभी संभावित परिणामों की खोज एल्गोरिदम बहुत जटिल है और आधारित है, अन्य बातों के साथ।
लेकिन कुछ मामलों में, गलत तरीके से सिंक्रनाइज़ किए गए कार्यक्रमों का उपयोग स्पष्ट रूप से उचित है। एक उदाहरण के रूप में, java.lang.String.hashCode () का कार्यान्वयन दें
 public final class String implements Serializable, Comparable<String>, CharSequence { /** Cache the hash code for the string */ private int hash; // Default to 0 ... public int hashCode() { int h = hash; if (h == 0 && count > 0) { ... hash = h; } return h; } ... } 

जब हैशकोड () विधि कहा जाता है, java.lang का एक उदाहरण। विभिन्न थ्रेड्स से अलग करने पर हैश फ़ील्ड पर डेटा रेस होगी।

हंस-जे का एक दिलचस्प लेख। बोहम, "नोंदेर्मिनिस्म अपरिहार्य है, लेकिन डेटा दौड़ शुद्ध बुराई हैं"
और रूसी में उसकी चर्चा
रुस्लान चेरामिन, "डेटा दौड़ शुद्ध बुराई है"


सिंक्रनाइज़ेशन क्या करता है?


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

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

कैश के संदर्भ में स्थिति पर चर्चा करते हुए, ऐसा लग सकता है कि ये मुद्दे केवल मल्टीप्रोसेसर मशीनों को प्रभावित करते हैं। हालांकि, एकल प्रोसेसर पर रीऑर्डरिंग प्रभाव आसानी से देखा जा सकता है। कंपाइलर मॉनिटर को कैप्चर करने से पहले या इसे जारी करने के बाद आपके कोड को स्थानांतरित नहीं कर सकता है। जब हम कहते हैं कि कैप्चरिंग और मॉनीटर जारी करना कैश को प्रभावित करता है, तो हम कई संभावित प्रभावों के लिए कमी का उपयोग करते हैं।

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

  1. एक धारा में प्रत्येक क्रिया "इस धारा के कोड में" कम "जाने वाली इस धारा में किसी भी अन्य कार्रवाई के पहले" होती है।
  2. मॉनीटर की रिलीज़ "प्रत्येक मॉनीटर के बाद के प्रत्येक कैप्चर" से पहले होती है।
  3. एक अस्थिर क्षेत्र के लिए लेखन एक ही अस्थिर क्षेत्र के प्रत्येक बाद के पढ़ने के लिए "पहले होता है" होता है।
  4. रनिंग थ्रेड में किसी भी एक्शन के "स्टार्ट से पहले" होता है।
  5. धारा में सभी क्रियाएं "पहले से होती हैं" (पहले होती हैं) किसी भी अन्य धागे की कोई क्रिया जो सफलतापूर्वक पहले धागे पर शामिल होने () पर प्रतीक्षा पूरी कर चुकी है।

इसका मतलब यह है कि किसी भी मेमोरी ऑपरेशन जो सिंक ब्लॉक से बाहर आने से पहले स्ट्रीम में दिखाई दे रहे थे, किसी अन्य स्ट्रीम को उसी मॉनिटर द्वारा संरक्षित सिंक्रोनाइज़्ड ब्लॉक में प्रवेश करने के बाद दिखाई देते हैं, क्योंकि सभी मेमोरी ऑपरेशन्स "मॉनिटर से पहले" होते हैं। , और मॉनिटर की रिहाई "कब्जा करने से पहले" होती है।
अनुवादक टिप्पणी
इस कार्यक्रम (डेटा - अस्थिर, रन - अस्थिर) को रोकने और 1 प्रिंट करने और पुराने और नए मेमोरी मॉडल में प्रिंट करने की गारंटी है
 public class App { static volatile int data = 0; static volatile boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = false; } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 


यह प्रोग्राम (डेटा - अस्थिर नहीं, रन - वोलेटाइल) को पुराने और नए मेमोरी मॉडल में बंद करने के लिए और पुराने में गारंटी है, लेकिन पुराने में यह 0 और 1 प्रिंट कर सकता है, और नए में यह प्रिंट 1 की गारंटी है। यह इस तथ्य के कारण है कि नए मेमोरी मॉडल में आप गैर-वाष्पशील, "उच्च" रिकॉर्ड को अस्थिरता में "बढ़ा" सकते हैं, लेकिन आप इसे "कम" नहीं कर सकते। और पुराने में यह "उठाना" और "इसे कम करना" संभव था।
 public class App { static int data = 0; static volatile boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = false; } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 


यह कार्यक्रम (डेटा - अस्थिर, रन - नहीं अस्थिर) दोनों मॉडल में बंद हो सकता है या नहीं भी हो सकता है। एक स्टॉप की स्थिति में, यह पुराने और नए मेमोरी मॉडल दोनों में 0 और 1 प्रिंट कर सकता है। यह इस तथ्य के कारण है कि दोनों मॉडलों में अस्थिरता में एक रिकॉर्ड से ऊपर गैर-वाष्पशील में एक रिकॉर्ड "उठाना" संभव है।
 public class App { static volatile int data = 0; static boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = false; } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 


यह कार्यक्रम (डेटा अस्थिर नहीं है, रन अस्थिर नहीं है) दोनों मॉडलों में बंद हो सकता है या नहीं भी हो सकता है। एक स्टॉप की स्थिति में, यह पुराने और नए मेमोरी मॉडल दोनों में 0 और 1 प्रिंट कर सकता है।
 public class App { static int data = 0; static boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = false; } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 


एक और परिणाम यह है कि निम्नलिखित पैटर्न, जिसका उपयोग कुछ लोग मेमोरी बैरियर सेट करने के लिए करते हैं, काम नहीं करता है:
 synchronized (new Object()) {} 

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

महत्वपूर्ण नोट : कृपया ध्यान दें कि संबंध सूत्र को ठीक से स्थापित करने से पहले दोनों थ्रेड के लिए एक ही मॉनिटर पर सिंक्रनाइज़ करना महत्वपूर्ण है। ऐसा तब नहीं होता है जब ए को स्ट्रीम ए पर दिखाई देने वाली हर चीज, जब वह ऑब्जेक्ट एक्स पर सिंक्रोनाइज़ की जाती है, तो ऑब्जेक्ट वाई पर सिंक्रोनाइज़ करने के बाद बी स्ट्रीम करने के लिए दृश्यमान हो जाती है। रिलीज और कैप्चर करना होगा "मैच" (अर्थात, एक ही मॉनीटर के साथ किया जाना चाहिए। ) ताकि सही शब्दार्थ प्रदान किए जा सकें। अन्यथा, कोड में डेटा रेस होती है।
अनुवादक टिप्पणी
निम्नलिखित प्रोग्राम दोनों मेमोरी मॉडल के भीतर या तो बंद हो सकते हैं या नहीं रुक सकते हैं (क्योंकि मॉनिटर और विभिन्न ऑब्जेक्ट्स के विभिन्न मॉनिटरों को अलग-अलग थ्रेड्स में लॉक और लॉक किया गया है)
 public class App { static Object lockA = new Object(); static Object lockB = new Object(); static boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { synchronized (lockA) { run = false; } } }).start(); while (true) { synchronized (lockB) { if (!run) break; } } } } 



ऐसा कैसे हो सकता है कि अंतिम क्षेत्र मानों को बदल दें?


अंतिम फ़ील्ड मान कैसे बदल सकते हैं इसके सर्वोत्तम उदाहरणों में स्ट्रिंग वर्ग का एक विशिष्ट कार्यान्वयन शामिल है।

तीन क्षेत्रों के साथ एक स्ट्रिंग को एक ऑब्जेक्ट के रूप में लागू किया जा सकता है - वर्णों की एक सरणी, इस सरणी में एक ऑफसेट और लंबाई। टाइप चार के एक क्षेत्र के रूप में लागू करने के बजाय इस तरह से एक स्ट्रिंग के कार्यान्वयन को चुनने का कारण [] हो सकता है कि यह कई लाइनों और StringBuffer वस्तुओं को एक ही चरित्र सरणी को साझा करने और अतिरिक्त ऑब्जेक्ट आवंटन और मेमोरी कॉपी से बचने की अनुमति देता है।इसलिए, उदाहरण के लिए, String.substring () पद्धति को एक नया स्ट्रिंग बनाकर लागू किया जा सकता है जो मूल स्ट्रिंग के साथ वर्णों के समान सरणी को साझा करता है और बस लंबाई और ऑफसेट फ़ील्ड में भिन्न होता है। स्ट्रिंग के सभी तीन क्षेत्र हैं - अंतिम।
अनुवादक टिप्पणी
Oracle से JRE 7 के लिए 6 अपडेट करने से पहले java.lang.String इस तरह से लागू किया जाता है
 public final class String implements Serializable, Comparable<String>, CharSequence { private final char[] value; private final int offset; private final int count; .... } 

update 6 JRE 7 Oracle java.lang.String ( offset count)
. , «» value « » ( ). :
1. null, value — null
2. null, value null, char[] value char-, 0-.

अनुवादक टिप्पणी
— String.substring(), char[] value
 public final class String implements Serializable, Comparable<String>, CharSequence { private final char[] value; private final int offset; private final int count; .... public String substring(int beginIndex, int endIndex) { .... return new String(offset + beginIndex, endIndex - beginIndex, value); } .... } 


— StringBuffer.toString(), String StringBuffer char[] value
 abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; ... } public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence { private final char[] value; private final int offset; private final int count; .... public synchronized String toString() { return new String(value, 0, count); } .... } 


 String s1 = "/usr/tmp"; String s2 = s1.substring(4); 


स्ट्रिंग s2 में 4 की एक ऑफसेट और 4 की लंबाई होगी। लेकिन पुराने मेमोरी मॉडल में, डिफ़ॉल्ट ऑफसेट वैल्यू (0) को देखने के लिए दूसरे थ्रेड के लिए संभव था, और बाद में 4. का सही मान देखने के लिए। ऐसा प्रतीत होता है। मानो स्ट्रिंग "/ usr" बदल गया है "/ tmp"।

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

नए मेमोरी मॉडल के साथ अंतिम फ़ील्ड कैसे काम करते हैं?


कंस्ट्रक्टर में ऑब्जेक्ट के अंतिम क्षेत्रों का मान निर्धारित किया जाता है। यदि हम मानते हैं कि ऑब्जेक्ट "सही ढंग से" बनाया गया है, तो जैसे ही ऑब्जेक्ट बनाया जाता है, कंस्ट्रक्टर में अंतिम फ़ील्ड को सौंपे गए मान सिंक्रनाइज़ेशन के बिना अन्य सभी थ्रेड्स को दिखाई देंगे। इसके अलावा, इन अंतिम फ़ील्ड्स द्वारा संदर्भित किसी अन्य ऑब्जेक्ट या सरणी के लिए दृश्यमान मान कम से कम "ताज़ा" अंतिम फ़ील्ड के मान के रूप में होंगे।
अनुवादक टिप्पणी
final- .
इतना
 public class App { final int k; public App10(int k) { this.k = k; } } 


 public class App { final int k; { this.k = 42; } } 


अनुवादक टिप्पणी
, ( instance — volatile, ). , "[1, 2]"
 import java.util.Arrays; public class App { final int[] data; public App() { this.data = new int[]{1, 2}; } static App instance; public static void main(String[] args) { new Thread(new Runnable() { public void run() { instance = new App(); } }).start(); while (instance == null) {/*NOP*/} System.out.println(Arrays.toString(instance.data)); } } 


, . , "[1, 0]" "[1, 2]" . , 1 final-.
 import java.util.Arrays; public class App { final int[] data; public App() { this.data = new int[]{1, 0}; this.data[1] = 2; } static App instance; public static void main(String[] args) { new Thread(new Runnable() { public void run() { instance = new App(); } }).start(); while (instance == null) {/*NOP*/} System.out.println(Arrays.toString(instance.data)); } } 


, . , "[1, 2]". 1 final-.
 import java.util.Arrays; public class App { final int[] data; public App() { int[] tmp = new int[]{1, 0}; tmp[1] = 2; this.data = tmp; } static App instance; public static void main(String[] args) { new Thread(new Runnable() { public void run() { instance = new App(); } }).start(); while (instance == null) {/*NOP*/} System.out.println(Arrays.toString(instance.data)); } } 



किसी वस्तु के "ठीक से निर्माण" होने का क्या मतलब है? इसका सीधा सा मतलब है कि ऑब्जेक्ट का संदर्भ उदाहरण निर्माण प्रक्रिया के पूरा होने तक "लीक" नहीं होता है ( उदाहरणों के लिए सुरक्षित निर्माण तकनीक देखें )।
अनुवादक टिप्पणी
( ): « » .

दूसरे शब्दों में, किसी भी स्थान पर केवल निर्माणाधीन वस्तु का लिंक न रखें जहाँ दूसरा धागा उसे देख सके। इसे किसी स्थिर फ़ील्ड में असाइन न करें, ऑब्जेक्ट को किसी अन्य ऑब्जेक्ट में श्रोता के रूप में पंजीकृत न करें, और इसी तरह। ये कार्य कंस्ट्रक्टर के पूरा होने (कंस्ट्रक्टर के बाहर, इसे कॉल करने के बाद) में किया जाना चाहिए, इसमें नहीं।
 class FinalFieldExample { final int x; int y; static FinalFieldExample f; public FinalFieldExample() { x = 3; y = 4; } static void writer() { f = new FinalFieldExample(); } static void reader() { if (f != null) { int i = fx; int j = fy; } } } 

अनुवादक टिप्पणी
, reader() writer() « » .


यह वर्ग इस बात का उदाहरण है कि अंतिम फ़ील्ड का उपयोग कैसे किया जाना चाहिए। पाठक को बुलाने वाला धागा () विधि एफएक्स में 3 पढ़ने की गारंटी है, क्योंकि यह अंतिम क्षेत्र है। लेकिन इसकी कोई गारंटी नहीं है कि यह y में 4 पढ़ेगा, क्योंकि यह एक गैर-अंतिम क्षेत्र है। यदि FinalFieldExample वर्ग का निर्माता इस तरह दिखाई देगा:
 public FinalFieldExample() { // bad! x = 3; y = 4; // bad construction - allowing this to escape global.obj = this; } 

तब इस बात की कोई गारंटी नहीं है कि जो धागा इस ऑब्जेक्ट के लिंक को Global.obj से पढ़ता है, वह x का 3 पढ़ेगा।

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

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

यदि आप अंतिम फ़ील्ड को बदलने के लिए जेएनआई का उपयोग करते हैं, तो व्यवहार अपरिभाषित है।
अनुवादक टिप्पणी
, Reflection API.


अस्थिर क्या करता है?


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

पुराने मेमोरी मॉडल के तहत, अस्थिर चरों की पहुंच एक-दूसरे के साथ फिर से नहीं की जा सकती है, लेकिन वे गैर-वाष्पशील चरों के साथ फिर से व्यवस्थित हो सकते हैं। इसने अस्थिरता वाले क्षेत्रों की उपयोगिता को एक धागे से दूसरे में सिग्नल प्रसारित करने के साधन के रूप में नकार दिया।

नए मेमोरी मॉडल के अनुसार, यह अभी भी सच है कि अस्थिर चर को एक दूसरे के साथ फिर से व्यवस्थित नहीं किया जा सकता है। अंतर यह है कि अब अस्थिर के बगल में स्थित सामान्य क्षेत्रों के बीच क्रम को बदलना इतना आसान नहीं है। वाष्पशील क्षेत्र में लिखने से मॉनिटर रिलीज़ के समान मेमोरी प्रभाव होता है, और वाष्पशील क्षेत्र से पढ़ने पर मॉनिटर के अधिग्रहण के समान मेमोरी प्रभाव होता है। वास्तव में, चूंकि नया मॉडल अस्थिर क्षेत्रों और अन्य क्षेत्रों (वाष्पशील या नियमित) तक पहुंच के बीच क्रम को बदलने पर सख्त प्रतिबंध लगाता है, इसलिए जब वह अस्थिर क्षेत्र के लिए लिखा गया था तब ए को स्ट्रीम करने के लिए दिखाई देने वाली सब कुछ धारा बी के लिए दिखाई देता है जब वह पढ़ेगा।
अनुवादक टिप्पणी
1 (data — volatile, run — volatile)
 public class App { static volatile int data = 0; static volatile boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = false; } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 

. 1, 0 1 (data — volatile, run — volatile), -volatile «» volatile, —
 public class App { static int data = 0; static volatile boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = false; } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 

(run — volatile «» ). , 1, 0 (data — volatile, run — volatile), -volatile
 public class App { static int data = 0; static boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = false; } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 

. (run — volatile «» ). 1
 public class App { static int data = 0; static boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = (data != 1); } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 

. , 1, 0 (data — volatile, run — volatile), -volatile «» volatile
 public class App { static volatile int data = 0; static boolean run = true; public static void main(String[] args) { new Thread(new Runnable() { public void run() { data = 1; run = false; } }).start(); while (run) {/*NOP*/}; System.out.println(data); } } 


यहाँ एक सरल उदाहरण है कि कैसे अस्थिर क्षेत्रों का उपयोग किया जा सकता है
 class VolatileExample { int x = 0; volatile boolean v = false; public void writer() { x = 42; v = true; } public void reader() { if (v == true) { //uses x - guaranteed to see 42. } } } 

हम एक धारा को एक लेखक और दूसरे को पाठक कहते हैंलेखक को v में रैम में डेटा डंप "d" लिखना और v से मेमोरी को इस मूल्य को "कैप्चर" करना पढ़ना। इस प्रकार, यदि पाठक फ़ील्ड v का सही मूल्य देखता है, तो यह x में मान 42 को देखने की गारंटी भी है। यह पुराने मेमोरी मॉडल के लिए सच नहीं था (पुराने में, यह गैर-वाष्पशील "लोअर" रिकॉर्ड को अस्थिर में "कम" करना संभव था)। यदि v अस्थिर नहीं थे, तो कंपाइलर लेखक में लिखने के क्रम को बदल सकता था , और पाठक x में 0 देख सकता था।
अनुवादक टिप्पणी
Joshua Bloch «Effective Java» 2nd edition (Item 66: Synchronize access to shared mutable data) «Java. » 1 ( 48. )


वाष्पशील शब्दार्थ काफी बढ़ाया गया था, लगभग सिंक्रनाइज़ के स्तर तक। दृश्यता के संदर्भ में प्रत्येक अस्थिर पढ़ने या लिखने के रूप में एक "आधा" के रूप में कार्य करता है।

महत्वपूर्ण नोट: कृपया ध्यान दें कि यह महत्वपूर्ण है कि दोनों धागे एक ही अस्थिर चर को पढ़ते हैं और लिखते हैं, ताकि रिश्ते से पहले ऐसा हो सके। यह वह स्थिति नहीं है जब वह सब कुछ जो ए स्ट्रीम को दिखाई देता है, जब वह वाष्पशील क्षेत्र f लिखता है, तो वह स्ट्रीम B को दृश्यमान हो जाता है जब वह वाष्पशील क्षेत्र g को मानता है। पढ़ना और लिखना एक ही अस्थिर चर को संदर्भित करना चाहिए ताकि उचित शब्दार्थ हो।

क्या नए डबल-चेक लॉकिंग मेमोरी मॉडल ने समस्या को हल किया?


(कुख्यात) डबल-चेकिंग लॉकिंग मुहावरा (जिसे मल्टीथ्रेडेड सिंगलटन पैटर्न भी कहा जाता है) एक ऐसी चाल है जिसे सिंक्रनाइज़ेशन ओवरहेड की अनुपस्थिति में विलंबित आरंभीकरण का समर्थन करने के लिए डिज़ाइन किया गया है। शुरुआती जेवीएम में, सिंक्रनाइज़ेशन धीमा था और डेवलपर्स इसे हटाना चाहते थे, शायद बहुत उत्साह से। डबल-चेक्ड लॉकिंग मुहावरे निम्नानुसार हैं:
 // double-checked-locking - don't do this! private static Something instance = null; public Something getInstance() { if (instance == null) { synchronized (this) { if (instance == null) instance = new Something(); } } return instance; } 

अनुवादक टिप्पणी
( , « » getInstance() — , this)
 // double-checked-locking - don't do this! public class Something { private static Something instance = null; public static Something getInstance() { if (instance == null) { synchronized (Something.class) { if (instance == null) instance = new Something(); } } return instance; } .... } 

Private Mutex
 // double-checked-locking - don't do this! public class Something { private static final Object lock = new Object(); private static Something instance = null; public static Something getInstance() { if (instance == null) { synchronized (lock) { if (instance == null) instance = new Something(); } } return instance; } .... } 



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

कई लोगों का मानना ​​था कि वाष्पशील कीवर्ड का उपयोग उन समस्याओं को हल करेगा जो डबल-चेक-लॉकिंग पैटर्न का उपयोग करने की कोशिश करते समय उत्पन्न होती हैं। 1.5 से पहले की आभासी मशीनों में, अस्थिर अभी भी सही संचालन की गारंटी नहीं देगा। नए मेमोरी मॉडल के अनुसार, एक क्षेत्र को अस्थिर घोषित करने से दोहरे-चेक-लॉकिंग के साथ समस्याओं को "ठीक" किया जाएगा, क्योंकि यह निर्माण स्ट्रीम के साथ कुछ को इनिशियलाइज़ करने और इसे रीडिंग स्ट्रीम में वापस करने के बीच एक "पहले होता है"।

हालांकि, डबल-चेक लॉकिंग के प्रशंसकों के लिए (और हम वास्तव में उम्मीद करते हैं कि वे चले गए हैं), समाचार अभी भी बहुत अच्छा नहीं है। डबल-चेक किए गए लॉकिंग का पूरा बिंदु सिंक्रनाइज़ेशन के ओवरहेड से बचने के लिए था। न केवल अल्पकालिक तुल्यकालन अब MUCH जावा 1.0 की तुलना में सस्ता है, बल्कि नए मेमोरी मॉडल के ढांचे के भीतर, वाष्पशील का उपयोग करते समय प्रदर्शन ड्रॉप लगभग सिंक्रनाइज़ेशन लागत के स्तर तक पहुंच गया है। इसलिए डबल-चेक लॉकिंग के साथ उपयोग करने के लिए अभी भी कोई अच्छा कारण नहीं है। संपादित: अधिकांश प्लेटफार्मों पर वाष्पशील सस्ता है।

इसके बजाय, डिमांड होल्डर मुहावरे पर इनिशियलाइज़ेशन का उपयोग करें, जो सुरक्षित और समझने में बहुत आसान है:
 private static class LazySomethingHolder { public static Something something = new Something(); } public static Something getInstance() { return LazySomethingHolder.something; } 

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

 public class Something { private static Something instance = null; public static Something getInstance() { return LazySomethingHolder.something; } .... private static class LazySomethingHolder { public static Something something = new Something(); } } 


  • LazySomethingHolder.something
  • «»

( «12.4.1. When Initialization Occurs» ):
A class or interface type T will be initialized immediately before the first occurrence of any one of the following:
  • ...
  • A static field declared by T is assigned.
  • ...

A class or interface will not be initialized under any other circumstance.

«12.4.2. Detailed Initialization Procedure» .


अगर मैं एक वर्चुअल मशीन लिखूं तो क्या होगा?


आपको http://gee.cs.oswego.edu/dl/jmm/cookbook.html देखना चाहिए

मुझे क्यों परेशान करना चाहिए?


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


संपर्क विवरण



मैं जावा प्रशिक्षण ऑनलाइन करता हूं (यहां प्रोग्रामिंग पाठ्यक्रम हैं ) और जावा कोर कोर्स के रीडिजाइन के भाग के रूप में प्रशिक्षण सामग्री का हिस्सा प्रकाशित करता हूं। आप YouTube चैनल पर दर्शकों के व्याख्यान की वीडियो रिकॉर्डिंग देख सकते हैं, शायद इस लेख में चैनल का वीडियो बेहतर व्यवस्थित है।

स्काइप: गोलोवचक्र्स
ईमेल: GolovachCourses@gmail.com

Source: https://habr.com/ru/post/In221133/


All Articles