जावा मेमोरी मॉडल का परिचय

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

मूल जावा मेमोरी मॉडल (जो, विशेष रूप से, "पेरकोलोकल मेमोरी" को संदर्भित करता है), जिसे 1995 में विकसित किया गया था, को विफलता माना जाता है: कोड सुरक्षा की गारंटी खोए बिना कई अनुकूलन नहीं किए जा सकते। विशेष रूप से, बहु-थ्रेडेड "सिंगल" लिखने के कई विकल्प हैं:

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

इसलिए, स्मृति तंत्र को नया रूप दिया गया है। 2005 में, जावा 5 की रिलीज़ के साथ, एक नया दृष्टिकोण प्रस्तुत किया गया था, जिसे जावा 14 की रिलीज़ के साथ और बेहतर बनाया गया था।

नया मॉडल तीन नियमों पर आधारित है:

नियम # 1 : सिंगल-थ्रेडेड प्रोग्राम छद्म अनुक्रमिक रूप से चलते हैं। इसका मतलब है: वास्तव में, प्रोसेसर प्रति घड़ी कई ऑपरेशन कर सकता है, एक ही समय में उनका क्रम बदल सकता है, हालांकि, सभी डेटा निर्भरता बनी रहती है, इसलिए व्यवहार अनुक्रमिक से भिन्न नहीं होता है।

नियम संख्या 2 : कहीं से भी कोई मूल्य नहीं हैं। किसी भी चर को पढ़ना (गैर-वाष्पशील लंबे और दोहरे को छोड़कर, जिसके लिए यह नियम लागू नहीं हो सकता है) या तो डिफ़ॉल्ट मान (शून्य) या किसी अन्य कमांड द्वारा लिखी गई कोई चीज़ लौटाएगा।

और नियम संख्या 3 : शेष घटनाओं को क्रम में निष्पादित किया जाता है, यदि वे एक सख्त आंशिक क्रम संबंध से जुड़े होते हैं "पहले निष्पादित होता है" ( पहले होता है )।

पहले होता है

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

इसमें कहा गया है कि दूसरी टीम पहले द्वारा किए गए परिवर्तनों से "पता" में होगी।

पहले होता है

उदाहरण के लिए, इस तरह के कार्यों के लिए एक को दूसरे से पहले निष्पादित किया जाता है:

तुल्यकालन और मॉनिटर:

  • मॉनिटर ( लॉक विधि , सिंक्रोनाइज़्ड स्टार्ट) को कैप्चर करना और उसके बाद उसी थ्रेड पर जो कुछ भी होता है।
  • मॉनिटर की वापसी (विधि अनलॉक , सिंक्रनाइज़ का अंत) और इससे पहले एक ही थ्रेड पर जो कुछ भी होता है।
  • मॉनिटर वापस करना और फिर इसे दूसरे थ्रेड द्वारा कैप्चर करना।

लिखना और पढ़ना:

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

वस्तु रखरखाव:

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

स्ट्रीम सेवा:

  • थ्रेड प्रारंभ करना और थ्रेड में कोई कोड।
  • थ्रेड से संबंधित ज़ीरोइंग चर और थ्रेड में कोई कोड।
  • थ्रेड में कोड और शामिल हों () ; थ्रेड में कोड और isAlive() == false है
  • इंटरप्ट() थ्रेड और पता लगाएं कि यह बंद हो गया है।

काम की बारीकियों से पहले होता है

एक ही मॉनिटर प्राप्त करने से पहले मॉनिटर होने से पहले एक होता है जारी करना। यह ध्यान देने योग्य है कि यह रिलीज़ है, न कि निकास, अर्थात, आपको प्रतीक्षा का उपयोग करते समय सुरक्षा के बारे में चिंता करने की आवश्यकता नहीं है।

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

public class Keeper {
    private Data data = null;

    public Data getData() {
        synchronized(this) {
            if(data == null) {
                data = new Data();
            }
        }

        return data;
    }
}

एक ही चर से पढ़ने से पहले एक अस्थिर चर के लिए लेखन होता है। बेशक, हमने जो बदलाव किया है, वह बग को ठीक करता है, लेकिन यह जिसने भी मूल कोड लिखा है, उसे वापस वहीं रख देता है, जहां से आया है - हर बार ब्लॉक कर देता है। अस्थिर कीवर्ड बचा सकता है। वास्तव में, विचाराधीन कथन का अर्थ है कि अस्थिर घोषित की गई हर चीज को पढ़ते समय, हमें हमेशा वास्तविक मूल्य मिलेगा।

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

तो, हमारे डबल-लॉकिंग मेढ़े पर वापस जाएं। अस्थिर का उपयोग करके, आप इस तरह की स्थिति को ठीक कर सकते हैं:

public class Keeper {
    private volatile Data data = null;

    public Data getData() {
        if(data == null) {
            synchronized(this) {
                if(data == null) {
                    data = new Data();
                }
            }
        }
        return data;
    }
}

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