जावा में अस्थिर और सिंक्रनाइज़ के बीच अंतर


233

मैं एक चर को घोषित करने volatileऔर हमेशा synchronized(this)जावा में एक ब्लॉक में चर को एक्सेस करने के बीच के अंतर पर सोच रहा हूं ?

इस लेख के अनुसार http://www.javamex.com/tutorials/synchronization_volatile.shtml कहा जाने वाला बहुत कुछ है और कई अंतर भी हैं लेकिन कुछ समानताएँ भी हैं।

मुझे इस जानकारी के टुकड़े में विशेष दिलचस्पी है:

...

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

उन्हें पढ़ने-अपडेट-लिखने से क्या मतलब है ? क्या लेखन भी अद्यतन नहीं है या क्या उनका सीधा सा मतलब है कि अद्यतन एक लेखन है जो पढ़ने पर निर्भर करता है?

सभी के अधिकांश, जब volatileएक synchronizedब्लॉक के माध्यम से उन्हें एक्सेस करने के बजाय चर घोषित करना अधिक उपयुक्त होता है ? क्या यह volatileउन चरों के लिए उपयोग करना अच्छा है जो इनपुट पर निर्भर हैं? उदाहरण के लिए, एक चर कहा जाता renderहै जिसे रेंडरिंग लूप के माध्यम से पढ़ा जाता है और एक कीपर ईवेंट द्वारा सेट किया जाता है?

जवाबों:


383

यह समझना महत्वपूर्ण है कि थ्रेड सुरक्षा के दो पहलू हैं ।

  1. निष्पादन नियंत्रण, और
  2. मेमोरी दृश्यता

जब कोड निष्पादित होता है तो नियंत्रण के साथ पहले करना होता है (उस क्रम में जिसमें निर्देश निष्पादित किए जाते हैं) और क्या यह समवर्ती रूप से निष्पादित हो सकता है, और दूसरा ऐसा करने के लिए जब क्या किया गया है की स्मृति में प्रभाव अन्य थ्रेड्स को दिखाई देते हैं। क्योंकि प्रत्येक CPU के बीच कैश का कई स्तर होता है और मुख्य मेमोरी, विभिन्न CPU या कोर पर चलने वाले थ्रेड्स "मेमोरी" को किसी भी समय अलग-अलग समय पर देख सकते हैं क्योंकि थ्रेड को मुख्य मेमोरी की निजी प्रतियों को प्राप्त करने और काम करने की अनुमति होती है।

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

volatileदूसरी ओर, उपयोग करना , सभी एक्सेस (वाचन या लिखना) को वाष्पशील चर में मुख्य मेमोरी में होने के लिए मजबूर करता है, जो कि प्रभावी रूप से वाष्पशील चर को सीपीयू कैश से बाहर रखता है। यह कुछ क्रियाओं के लिए उपयोगी हो सकता है जहां यह आवश्यक है कि चर की दृश्यता सही हो और अभिगम का क्रम महत्वपूर्ण न हो। का उपयोग कर के volatileउपचार में परिवर्तन करने के लिए longऔर doubleउन तक पहुँचने के लिए उन्हें परमाणु की आवश्यकता होती है; कुछ (पुराने) हार्डवेयर पर इसे ताले की आवश्यकता हो सकती है, हालांकि आधुनिक 64 बिट हार्डवेयर पर नहीं। जावा 5+ के लिए नए (JSR-133) मेमोरी मॉडल के तहत, अस्थिरता के शब्दार्थ को मेमोरी विजिबिलिटी और इंस्ट्रक्शन आर्डर ( http://www.cs.umd.edu देखें) के संबंध में लगभग मजबूत किया गया है । /users/pugh/java/memoryModel/jsr-133-faq.html#volatile)। दृश्यता के प्रयोजनों के लिए, एक अस्थिर क्षेत्र में प्रत्येक का उपयोग आधा तुल्यकालन की तरह होता है।

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

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

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

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

// Declaration
public class SharedLocation {
    static public SomeObject someObject=new SomeObject(); // default object
    }

// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
//       someObject will be internally consistent for xxx(), a subsequent 
//       call to yyy() might be inconsistent with xxx() if the object was 
//       replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
private String getError() {
    SomeObject myCopy=SharedLocation.someObject; // gets current copy
    ...
    int cod=myCopy.getErrorCode();
    String txt=myCopy.getErrorText();
    return (cod+" - "+txt);
    }
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

खासतौर पर अपने रीड-अपडेट-राइट सवाल पर बोलना। निम्नलिखित असुरक्षित कोड पर विचार करें:

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

अब अपडेटकाउंटर () विधि अनसंकट्रेंडेड के साथ, एक ही समय में दो धागे इसे दर्ज कर सकते हैं। क्या हो सकता है के कई क्रमों के बीच, एक यह है कि थ्रेड -1 काउंटर == 1000 के लिए परीक्षण करता है और इसे सही पाता है और फिर निलंबित कर दिया जाता है। फिर थ्रेड -2 एक ही परीक्षण करता है और इसे सही भी देखता है और निलंबित कर दिया जाता है। फिर थ्रेड -1 फिर से शुरू होता है और काउंटर को 0. पर सेट करता है और फिर थ्रेड -2 फिर से शुरू होता है और फिर से 0 पर सेट होता है क्योंकि यह थ्रेड -1 से अपडेट से चूक गया था। यह तब भी हो सकता है जब थ्रेड स्विचिंग नहीं होती है जैसा कि मैंने वर्णित किया है, लेकिन सिर्फ इसलिए कि काउंटर की दो अलग-अलग कैश्ड प्रतियां दो अलग-अलग सीपीयू कोर में मौजूद थीं और प्रत्येक थ्रेड एक अलग कोर पर चलता था। उस बात के लिए, एक धागा एक मूल्य पर काउंटर हो सकता है और दूसरा केवल कैशिंग के कारण कुछ पूरी तरह से अलग मूल्य पर काउंटर हो सकता है।

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

MOV EAX,counter
INC EAX
MOV counter,EAX

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


5
बहुत बहुत धन्यवाद! काउंटर के साथ उदाहरण समझने में सरल है। हालांकि, जब चीजें वास्तविक हो जाती हैं, तो यह थोड़ा अलग होता है।
एल्बस डंबलडोर

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

1
@ वार्निश यह समान नहीं है, लेकिन इसमें शामिल थ्रेड्स के स्थानीय कैश शामिल होंगे। ।
लॉरेंस Dol

1
@ MarianPa Mardzioch: वेतन वृद्धि या वेतन वृद्धि पढ़ना या लिखना नहीं है, यह पढ़ना और लिखना है; यह एक रजिस्टर में पढ़ा जाता है, फिर एक रजिस्टर इंक्रीमेंट, फिर मेमोरी में वापस लिखता है। पढ़ता और लिखता है व्यक्तिगत रूप से परमाणु, लेकिन ऐसे कई ऑपरेशन नहीं हैं।
लॉरेंस Dol

2
इसलिए, अक्सर पूछे जाने वाले प्रश्नों के अनुसार, ताला खोलने के बाद से केवल किए गए कार्यों को अनलॉक करने के बाद दिखाई देता है, बल्कि उस धागे द्वारा की गई सभी क्रियाएं दिखाई देती हैं। यहां तक ​​कि ताला अधिग्रहण से पहले की गई कार्रवाई।
Lii

97

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

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1()i1वर्तमान थ्रेड में वर्तमान में संग्रहीत मान तक पहुँचता है । थ्रेड्स में वैरिएबल की स्थानीय प्रतियां हो सकती हैं, और डेटा को अन्य थ्रेड्स में मौजूद डेटा के समान नहीं होना चाहिए। विशेष रूप से, एक अन्य थ्रेड में थ्रेड अद्यतन i1किया जा सकता है , लेकिन वर्तमान थ्रेड में मान इससे भिन्न हो सकता है अद्यतन मूल्य। वास्तव में जावा में "मुख्य" मेमोरी का विचार है, और यह वह मेमोरी है जो चर के लिए वर्तमान "सही" मान रखती है। थ्रेड्स के चर के लिए डेटा की अपनी प्रतिलिपि हो सकती है, और थ्रेड कॉपी "मुख्य" मेमोरी से अलग हो सकती है। तो वास्तव में, "मुख्य" मेमोरी के लिए 1 का मान होना संभव है i1, थ्रेड 1 के लिए 2 का मान i1और थ्रेड 2 के लिएअगर थ्रेड 1 और थ्रेड 2 में अपडेटेड i1 है , तो 3 का मान होना चाहिए, लेकिन उन अपडेटेड वैल्यू को अभी तक "मुख्य" मेमोरी या अन्य थ्रेड्स में प्रचारित नहीं किया गया है।i1

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

वॉलिटाइल और सिंक्रनाइज़ के बीच दो अंतर हैं।

सबसे पहले सिंक्रोनाइज्ड प्राप्त होता है और मॉनिटर पर ताले लगाता है जो एक कोड ब्लॉक को निष्पादित करने के लिए एक बार में केवल एक थ्रेड को मजबूर कर सकता है। सिंक्रनाइज़ करने के लिए यह काफी प्रसिद्ध पहलू है। लेकिन सिंक्रनाइज़ भी मेमोरी सिंक्रनाइज़ करता है। वास्तव में सिंक्रोनाइज़ पूरी थ्रेड मेमोरी को "मुख्य" मेमोरी के साथ सिंक्रनाइज़ करता है। इसलिए निष्पादन geti3()निम्नलिखित है:

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

इसलिए जहां अस्थिरता केवल थ्रेड मेमोरी और "मेन" मेमोरी के बीच एक वैरिएबल के मूल्य को सिंक्रोनाइज़ करती है, वहीं सिंक्रोनाइज़ थ्रेड मेमोरी और "मेन" मेमोरी के बीच सभी वेरिएबल्स के वैल्यू को सिंक्रोनाइज़ करता है और बूट के लिए एक मॉनिटर को रिलीज़ करता है। स्पष्ट रूप से सिंक्रनाइज़ अस्थिरता से अधिक ओवरहेड होने की संभावना है।

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html


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

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

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

20

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

volatileचर पहुंच संशोधक है जो मुख्य स्मृति से चर का नवीनतम मूल्य प्राप्त करने के लिए सभी थ्रेड्स को बाध्य करता है। volatileचर तक पहुँचने के लिए किसी लॉकिंग की आवश्यकता नहीं है । सभी धागे एक ही समय में अस्थिर चर मान तक पहुंच सकते हैं।

अस्थिर चर का उपयोग करने के लिए एक अच्छा उदाहरण: Dateचर।

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

यहां छवि विवरण दर्ज करें

अवधारणा की बेहतर समझ के लिए इस लेख पर एक नज़र डालें volatile

लॉरेंस डोल ने आपके बारे में बताया read-write-update query

आपके अन्य प्रश्नों के बारे में

जब यह उन्हें सिंक्रनाइज़ के माध्यम से पहुँच से अधिक अस्थिर घोषित करने के लिए अधिक उपयुक्त है?

volatileयदि आपको लगता है कि सभी थ्रेड्स को वास्तविक समय में वैरिएबल का वास्तविक मान मिलना चाहिए, जैसे कि मैंने तिथि चर के लिए समझाया है, तो आपको इसका उपयोग करना होगा।

क्या इनपुट पर निर्भर चर के लिए अस्थिर का उपयोग करना एक अच्छा विचार है?

उत्तर पहले प्रश्न के समान होगा।

बेहतर समझ के लिए इस लेख का संदर्भ लें ।


तो एक ही समय में पढ़ा जा सकता है, और सभी थ्रेड नवीनतम मान पढ़ेंगे क्योंकि CPU मुख्य मेमोरी को CPU थ्रेड कैश नहीं करता है, लेकिन लिखने के बारे में क्या? लिखना समवर्ती सही नहीं होना चाहिए? दूसरा प्रश्न: यदि कोई ब्लॉक सिंक्रनाइज़ किया गया है, लेकिन चर अस्थिर नहीं है, तो एक सिंक्रनाइज़ किए गए ब्लॉक में एक चर का मान अभी भी दूसरे कोड ब्लॉक में दूसरे धागे से बदल सकता है?
the_prole

11

tl; डॉआर :

मल्टीथ्रेडिंग के साथ 3 मुख्य मुद्दे हैं:

1) दौड़ की स्थिति

2) कैशिंग / बासी स्मृति

3) शिकायतकर्ता और सीपीयू अनुकूलन

volatile2 और 3 को हल कर सकते हैं, लेकिन 1 को हल नहीं कर सकते synchronized/ स्पष्ट ताले 1, 2 और 3 को हल कर सकते हैं।

विस्तार :

1) इस थ्रेड असुरक्षित कोड पर विचार करें:

x++;

हालांकि यह एक ऑपरेशन की तरह लग सकता है, यह वास्तव में 3 है: स्मृति से x का वर्तमान मूल्य पढ़ना, इसमें 1 जोड़ना, और इसे वापस मेमोरी में सहेजना। यदि कुछ धागे एक ही समय में करने की कोशिश करते हैं, तो ऑपरेशन का परिणाम अपरिभाषित होता है। यदि xमूल रूप से 1 था, तो 2 थ्रेड्स कोड को संचालित करने के बाद यह 2 हो सकता है और यह 3 हो सकता है, इस पर निर्भर करता है कि किस धागे ने नियंत्रण से पहले ऑपरेशन का कौन सा हिस्सा दूसरे धागे में स्थानांतरित किया था। यह नस्ल की स्थिति का एक रूप है ।

synchronizedकोड के एक ब्लॉक पर उपयोग करने से यह परमाणु बन जाता है - इसका अर्थ यह है कि जैसे कि 3 ऑपरेशन एक ही बार में होते हैं, और बीच में आने के लिए एक और सूत्र के लिए कोई रास्ता नहीं है और हस्तक्षेप करते हैं। तो अगर x1 था, और 2 थ्रेड्स को x++हम पहले से जानते हैं, तो इसके बराबर होने की कोशिश करेंगे 3. यह दौड़ की समस्या को हल करता है।

synchronized (this) {
   x++; // no problem now
}

के xरूप में चिह्नित volatileकरना x++;परमाणु नहीं बनाता है , इसलिए यह इस समस्या को हल नहीं करता है।

2) इसके अलावा, थ्रेड्स का अपना संदर्भ है - अर्थात वे मुख्य मेमोरी से मूल्यों को कैश कर सकते हैं। इसका मतलब है कि कुछ थ्रेड्स में एक वैरिएबल की प्रतियां हो सकती हैं, लेकिन वे अन्य थ्रेड्स के बीच वेरिएबल की नई स्थिति को साझा किए बिना उनकी वर्किंग कॉपी पर काम करते हैं।

, एक धागे पर विचार करें कि x = 10;। और कुछ बाद में, एक और धागा में, x = 20;xपहले थ्रेड में मान का परिवर्तन प्रकट नहीं हो सकता है, क्योंकि दूसरे थ्रेड ने नई मान को उसकी कार्यशील मेमोरी में सहेज लिया है, लेकिन उसने इसे मुख्य मेमोरी में कॉपी नहीं किया है। या कि यह इसे मुख्य मेमोरी में कॉपी करता है, लेकिन पहले थ्रेड ने इसकी कार्यशील प्रति अपडेट नहीं की है। तो अगर अब पहला धागा जाँचता है if (x == 20)तो उत्तर होगा false

एक चर को volatileमूल रूप से चिह्नित करना सभी धागे को मुख्य मेमोरी पर केवल पढ़ने और लिखने के लिए कहता है। synchronizedब्लॉक में प्रवेश करने पर प्रत्येक मेमोरी को मुख्य मेमोरी से अपडेट करने के लिए कहता है, और ब्लॉक से बाहर निकलने पर परिणाम को वापस मुख्य मेमोरी में फ्लश करता है।

ध्यान दें कि डेटा रेस के विपरीत, बासी मेमोरी इतनी आसानी से (री) उत्पादन के लिए आसान नहीं है, क्योंकि मुख्य मेमोरी में फ्लश वैसे भी होते हैं।

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

निम्नलिखित कोड पर विचार करें:

boolean b = false;
int x = 10;

void threadA() {
    x = 20;
    b = true;
}

void threadB() {
    if (b) {
        System.out.println(x);
    }
}

आपको लगता है कि थ्रेडबी केवल 20 प्रिंट कर सकता है (या कुछ भी प्रिंट नहीं कर सकता है यदि थ्रेडबी अगर चेक-सेट bको सच होने से पहले निष्पादित किया जाता है), जैसा bकि x20 पर सेट होने के बाद ही सही पर सेट होता है, लेकिन कंपाइलर / सीपीयू फिर से तय कर सकता है threadA, उस मामले में threadB भी 10 अंकन प्रिंट कर सकता है bके रूप में volatileसुनिश्चित है कि यह पुनर्क्रमित नहीं की जाएगी (या कुछ मामलों में खारिज कर दिया)। कौन सा मतलब थ्रेडबी केवल 20 (या कुछ भी नहीं) प्रिंट कर सकता है। सिंक्रोनाइज़ किए गए तरीकों को चिह्नित करने से समान परिणाम प्राप्त होगा। इसके अलावा एक चर को चिह्नित करना volatileकेवल यह सुनिश्चित करता है कि यह फिर से व्यवस्थित नहीं होगा, लेकिन इससे पहले / बाद में सब कुछ अभी भी फिर से व्यवस्थित किया जा सकता है, इसलिए सिंक्रनाइज़ेशन कुछ परिदृश्यों में अधिक अनुकूल हो सकता है।

ध्यान दें कि जावा 5 न्यू मेमोरी मॉडल से पहले, वाष्पशील ने इस समस्या को हल नहीं किया था।


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

@ ज़िन इसलिए जब मैं x ++ के लिए एक सिंक्रोनाइज़्ड ब्लॉक बनाता हूं, तो क्या इसे एक झंडे वाले परमाणु इंक / DEC में बदल देता है या यह एक नियमित लॉक का उपयोग करता है?
डेविड रेफेली

मुझे नहीं पता! मुझे क्या पता है कि INC / DEC परमाणु नहीं हैं क्योंकि एक CPU के लिए, इसे मूल्य को लोड करना होगा और इसे READ करना चाहिए और इसे (स्मृति में) भी लिखना चाहिए, किसी अन्य अंकगणितीय ऑपरेशन की तरह।
लाश
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.