गैर-अंतिम फ़ील्ड का सिंक्रनाइज़ेशन


91

हर बार जब मैं एक गैर-अंतिम वर्ग फ़ील्ड पर सिंक्रनाइज़ करता हूं, तो एक चेतावनी दिखाई दे रही है। यहाँ कोड है:

public class X  
{  
   private Object o;  

   public void setO(Object o)  
   {  
     this.o = o;  
   }  

   public void x()  
   {  
     synchronized (o) // synchronization on a non-final field  
     {  
     }  
   }  
 } 

इसलिए मैंने निम्नलिखित तरीके से कोडिंग को बदल दिया:

 public class X  
 {  

   private final Object o;       
   public X()
   {  
     o = new Object();  
   }  

   public void x()  
   {  
     synchronized (o)
     {  
     }  
   }  
 }  

मुझे यकीन नहीं है कि उपरोक्त कोड गैर-अंतिम वर्ग क्षेत्र पर सिंक्रनाइज़ करने का उचित तरीका है। मैं एक गैर अंतिम फ़ील्ड को कैसे सिंक्रनाइज़ कर सकता हूं?

जवाबों:


127

सबसे पहले, मैं आपको वास्तव में कड़ी मेहनत के उच्च स्तर पर संगामिति के मुद्दों से निपटने के लिए प्रोत्साहित करने के लिए प्रोत्साहित करता हूं, अर्थात इसे java.util.concurrent से कक्षाओं का उपयोग करके हल करना , जैसे ExecutorServices, Callables, Futures आदि।

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

उस वस्तु पर सिंक्रोनाइज़ करें, जिसकी आपको अनन्य पहुँच की आवश्यकता होती है (या, अभी तक, इसे सुरक्षित रखने के लिए समर्पित कोई वस्तु)।


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

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

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

1
यह एक और अच्छा बिंदु है, और मैं सहमत हूं; एक गैर-अंतिम चर पर ताला लगाने से निश्चित रूप से सावधानीपूर्वक औचित्य की आवश्यकता होती है।
aioobe

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

47

यह वास्तव में एक अच्छा विचार नहीं है - क्योंकि आपके सिंक्रनाइज़ ब्लॉक नहीं रह रहे हैं वास्तव में एक सुसंगत तरीके से सिंक्रनाइज़।

माना जाता है कि सिंक्रनाइज़ किए गए ब्लॉक यह सुनिश्चित करने के लिए हैं कि केवल एक थ्रेड एक बार में कुछ साझा किए गए डेटा तक पहुंचता है, विचार करें:

  • थ्रेड 1 सिंक्रनाइज़ ब्लॉक में प्रवेश करता है। Yay - इसमें साझा किए गए डेटा की अनन्य पहुंच है ...
  • थ्रेड 2 कॉल सेटो ()
  • थ्रेड 3 (या अभी भी 2 ...) सिंक्रनाइज़ ब्लॉक में प्रवेश करता है। EEK! ऐसा लगता है कि यह साझा डेटा तक अनन्य पहुंच रखता है, लेकिन थ्रेड 1 अभी भी इसके साथ है ...

तुम क्यों हैं चाहते हैं ऐसा करने के लिए? हो सकता है कि कुछ बहुत ही विशिष्ट परिस्थितियाँ हैं जहाँ यह समझ में आता है ... लेकिन आपको एक विशिष्ट उपयोग के मामले के साथ (साथ ही ऊपर दिए गए परिदृश्य के प्रकार को कम करने के तरीकों के साथ) प्रस्तुत करना होगा, इससे पहले कि मैं इससे खुश होऊँ यह।


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

2
@ फ़्लेप: ऐसा लगता है कि आपको एक अलग प्रश्न के रूप में अधिक विस्तृत प्रश्न पूछना चाहिए - लेकिन हाँ, मैं अक्सर अलग-अलग वस्तुओं को ताले के रूप में बनाऊंगा।
जॉन स्कीट

3
@VitBernatik: नहीं। यदि थ्रेड X कॉन्फ़िगरेशन को संशोधित करना शुरू कर देता है, तो थ्रेड Y परिवर्तनशील होने के चर का मान बदल देता है, तो थ्रेड Z कॉन्फ़िगरेशन को संशोधित करना शुरू कर देता है, तो X और Z दोनों एक ही समय में कॉन्फ़िगरेशन को संशोधित करेंगे, जो कि खराब है ।
जॉन स्कीट

1
संक्षेप में, यह सुरक्षित है अगर हम हमेशा ऐसी लॉक ऑब्जेक्ट्स को अंतिम घोषित करते हैं, सही है?
सेंटऑनारियो

2
@LinkTheProgrammer: "एक सिंक्रोनाइज़ की गई विधि उदाहरण में हर एक ऑब्जेक्ट को सिंक्रोनाइज़ करती है" - नहीं। यह बस सच नहीं है, और आपको सिंक्रनाइज़ेशन की अपनी समझ को फिर से देखना चाहिए।
जॉन स्कीट

12

मैं जॉन की एक टिप्पणी से सहमत हूं: आपको चर के संदर्भ परिवर्तनों के मामले में विसंगतियों को रोकने के लिए गैर-अंतिम चर का उपयोग करते हुए हमेशा एक अंतिम लॉक डमी का उपयोग करना चाहिए । तो किसी भी मामले में और अंगूठे के पहले नियम के रूप में:

नियम # 1: यदि कोई क्षेत्र गैर-अंतिम है, तो हमेशा (निजी) अंतिम लॉक डमी का उपयोग करें।

कारण # 1: आप लॉक को पकड़ते हैं और चर के संदर्भ को अपने आप से बदल देते हैं। सिंक्रनाइज़ लॉक के बाहर प्रतीक्षा कर रहा एक और धागा पहरेदार ब्लॉक में प्रवेश करने में सक्षम होगा।

कारण # 2: आप लॉक को पकड़ते हैं और एक अन्य थ्रेड वैरिएबल के संदर्भ को बदल देता है। परिणाम समान है: एक और धागा संरक्षित ब्लॉक में प्रवेश कर सकता है।

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

नियम # 2: एक गैर-अंतिम वस्तु को लॉक करते समय आपको हमेशा दोनों करने की आवश्यकता होती है: अंतिम चरण डमी का उपयोग करना और रैम सिंक्रनाइज़ेशन के लिए गैर-अंतिम ऑब्जेक्ट का लॉक। (एकमात्र विकल्प ऑब्जेक्ट के सभी क्षेत्रों को अस्थिर घोषित करेगा!)

इन तालों को "नेस्टेड लॉक" भी कहा जाता है। ध्यान दें कि आपको उन्हें हमेशा उसी क्रम में कॉल करना होगा, अन्यथा आपको एक डेड लॉक मिलेगा :

public class X {
    private final LOCK;
    private Object o;

    public void setO(Object o){
        this.o = o;  
    }  

    public void x() {
        synchronized (LOCK) {
        synchronized(o){
            //do something with o...
        }
        }  
    }  
} 

जैसा कि आप देख सकते हैं कि मैं दो ताले सीधे एक ही पंक्ति पर लिखता हूं, क्योंकि वे हमेशा एक साथ होते हैं। इस तरह, आप 10 नेस्टिंग लॉक भी कर सकते हैं:

synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
    //entering the locked space
}
}
}
}

ध्यान दें कि यह कोड नहीं टूटेगा यदि आप synchronized (LOCK3)किसी अन्य थ्रेड्स की तरह केवल एक आंतरिक लॉक प्राप्त करते हैं । लेकिन यह टूट जाएगा यदि आप किसी अन्य धागे में कुछ इस तरह कहते हैं:

synchronized (LOCK4) {
synchronized (LOCK1) {  //dead lock!
synchronized (LOCK3) {
synchronized (LOCK2) {
    //will never enter here...
}
}
}
}

गैर-अंतिम फ़ील्ड को हैंडल करते समय ऐसे नेस्टेड लॉक के चारों ओर केवल एक वर्कअराउंड है:

नियम # 2 - वैकल्पिक: ऑब्जेक्ट के सभी क्षेत्रों को अस्थिर के रूप में घोषित करें। (मैं ऐसा करने के नुकसान के बारे में यहां बात नहीं करूंगा, जैसे कि एक्स-लेवल कैश में किसी भी स्टोरेज को रीड्स, एसो के लिए रोकना।)

इसलिए aioobe काफी सही है: बस java.util.concurrent का उपयोग करें। या सिंक्रनाइज़ेशन के बारे में सब कुछ समझना शुरू करें और इसे नेस्टेड ताले के साथ खुद करें। ;)

अधिक विवरण के लिए, गैर-अंतिम फ़ील्ड पर सिंक्रनाइज़ेशन क्यों टूट जाता है, मेरे परीक्षण मामले पर नज़र डालें: https://stackoverflow.com/a/21460055/2012947

और अधिक जानकारी के लिए आपको रैम और कैश के कारण सभी चीजों को सिंक्रनाइज़ करने की आवश्यकता क्यों है, यहां एक नज़र डालें: https://stackoverflow.com/a/21409975/2012947


1
मुझे लगता है कि आपको oऑब्जेक्ट सेट करने और पढ़ने के बीच एक "होता है-पहले" संबंध स्थापित करने के लिए एक सिंक्रनाइज़ (LOCK) के सेटर को लपेटना होगा o। मैं इस बारे में एक ऐसे ही सवाल पर चर्चा कर रहा हूँ: stackoverflow.com/questions/32852464/…
पेट्राकेश

मैं dataObject के सदस्यों तक पहुंच को सिंक्रनाइज़ करने के लिए dataObject का उपयोग करता हूं। यह कैसे गलत है? यदि dataObject कहीं अलग इंगित करना शुरू करता है, तो मैं चाहता हूं कि इसे संशोधित करने के लिए समवर्ती थ्रेड को रोकने के लिए नए डेटा पर सिंक्रनाइज़ किया जाए। उस के साथ कोई समस्या?
हरमन

2

मैं वास्तव में यहाँ सही उत्तर नहीं देख रहा हूँ, अर्थात यह करना बिल्कुल ठीक है।

मुझे भी यकीन नहीं है कि यह चेतावनी क्यों है, इसमें कुछ भी गलत नहीं है। JVM यह सुनिश्चित करता है कि जब आप कोई मूल्य पढ़ते हैं तो आपको कुछ वैध वस्तु वापस मिलती है (या अशक्त), और आप किसी भी वस्तु पर सिंक्रनाइज़ कर सकते हैं ।

यदि आप उपयोग करते समय वास्तव में लॉक को बदलने की योजना बनाते हैं (जैसे कि इसका उपयोग करने से पहले इसे init विधि से बदलने के विपरीत, तो इसका उपयोग शुरू करने से पहले), आपको वह चर बनाना होगा जिसे आप बदलने की योजना बनाते हैं volatile। फिर आपको बस इतना करना है कि पुरानी और नई वस्तु दोनों पर सिंक्रनाइज़ करना है , और आप सुरक्षित रूप से मूल्य बदल सकते हैं

public volatile Object lock;

...

synchronized (lock) {
    synchronized (newObject) {
        lock = newObject;
    }
}

वहाँ। यह जटिल नहीं है, ताले (म्यूटेक्स) के साथ कोड लिखना वास्तव में काफी आसान है। उनके बिना कोड लिखना (लॉक फ्री कोड) कठिन क्या है।


यह काम नहीं कर सकता है। कहते हैं कि O1 O के रूप में शुरू हुआ, फिर T1 को ओ (= O1) और O2 को थ्रेड करें और O को O2 पर सेट करें। उसी समय थ्रेड T2 O1 को लॉक कर देता है और T1 के अनलॉक होने का इंतजार करता है। जब यह लॉक O1 प्राप्त करता है, तो यह O3 पर सेट हो जाएगा। इस परिदृश्य में, O1 और T2 लॉकिंग O1 के बीच, O1 O के माध्यम से लॉक करने के लिए O1 अमान्य हो गया। इस समय एक और धागा लॉकिंग के लिए ओ (= O2) का उपयोग कर सकता है और टी 2 के साथ दौड़ में निर्बाध रूप से आगे बढ़ सकता है।
जीपीएस

2

EDIT: तो यह समाधान (जैसा कि जॉन स्कीट द्वारा सुझाया गया है) में "सिंक्रनाइज़ (ऑब्जेक्ट) {}" के कार्यान्वयन की परमाणुता के साथ एक मुद्दा हो सकता है जबकि ऑब्जेक्ट संदर्भ बदल रहा है। मैंने अलग से पूछा और श्री इरिकसन के अनुसार यह धागा सुरक्षित नहीं है - देखें: क्या सिंक्रनाइज़ ब्लॉक परमाणु में प्रवेश कर रहा है? । इसलिए इसे उदाहरण के रूप में लें कि यह कैसे न करें - लिंक के साथ क्यों;)

कोड देखें कि यह कैसे काम करेगा यदि सिंक्रनाइज़ () परमाणु होगा:

public class Main {
    static class Config{
        char a='0';
        char b='0';
        public void log(){
            synchronized(this){
                System.out.println(""+a+","+b);
            }
        }
    }

    static Config cfg = new Config();

    static class Doer extends Thread {
        char id;

        Doer(char id) {
            this.id = id;
        }

        public void mySleep(long ms){
            try{Thread.sleep(ms);}catch(Exception ex){ex.printStackTrace();}
        }

        public void run() {
            System.out.println("Doer "+id+" beg");
            if(id == 'X'){
                synchronized (cfg){
                    cfg.a=id;
                    mySleep(1000);
                    // do not forget to put synchronize(cfg) over setting new cfg - otherwise following will happend
                    // here it would be modifying different cfg (cos Y will change it).
                    // Another problem would be that new cfg would be in parallel modified by Z cos synchronized is applied on new object
                    cfg.b=id;
                }
            }
            if(id == 'Y'){
                mySleep(333);
                synchronized(cfg) // comment this and you will see inconsistency in log - if you keep it I think all is ok
                {
                    cfg = new Config();  // introduce new configuration
                    // be aware - don't expect here to be synchronized on new cfg!
                    // Z might already get a lock
                }
            }
            if(id == 'Z'){
                mySleep(666);
                synchronized (cfg){
                    cfg.a=id;
                    mySleep(100);
                    cfg.b=id;
                }
            }
            System.out.println("Doer "+id+" end");
            cfg.log();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Doer X = new Doer('X');
        Doer Y = new Doer('Y');
        Doer Z = new Doer('Z');
        X.start();
        Y.start();
        Z.start();
    }

}

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

@Jon। जवाब के लिए thx! आपकी चिंता सुनता हूं। मैं इस मामले के लिए सहमत हूं कि बाहरी लॉक "सिंक्रनाइज़ परमाणुकरण" के सवाल से बचना होगा। इस प्रकार बेहतर होगा। हालांकि ऐसे मामले हो सकते हैं जहां आप रनटाइम अधिक कॉन्फ़िगरेशन में पेश करना चाहते हैं और विभिन्न थ्रेड समूहों के लिए अलग कॉन्फ़िगरेशन साझा करते हैं (हालांकि यह मेरा मामला नहीं है)। और फिर यह समाधान दिलचस्प हो सकता है। मैंने प्रश्न stackoverflow.com/questions/29217266/… को सिंक्रोनाइज़ () की परमाणुता के साथ पोस्ट किया है - इसलिए हम देखेंगे कि क्या इसका उपयोग किया जा सकता है (और किसी का उत्तर है)
Vit Bernatik

2

AtomicReference आपकी आवश्यकता के लिए सूट करता है।

परमाणु पैकेज के बारे में जावा प्रलेखन से :

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

boolean compareAndSet(expectedValue, updateValue);

नमूना कोड:

String initialReference = "value 1";

AtomicReference<String> someRef =
    new AtomicReference<String>(initialReference);

String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);

उपरोक्त उदाहरण में, आप Stringअपने स्वयं के साथ प्रतिस्थापित करते हैंObject

संबंधित एसई प्रश्न:

जावा में AtomicReference का उपयोग कब करें?


1

यदि oकिसी उदाहरण के जीवनकाल के लिए कभी नहीं बदलता है X, तो दूसरा संस्करण बेहतर शैली है चाहे वह सिंक्रोनाइज़ेशन शामिल हो।

अब, पहले संस्करण के साथ कुछ भी गलत है या नहीं, यह जानना असंभव है कि उस कक्षा में और क्या चल रहा है। मैं संकलक के साथ सहमत होना चाहूंगा कि यह त्रुटि-प्रवण दिखता है (मैं दूसरों ने जो कहा है उसे दोहराना नहीं होगा)।


1

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

मुझे लगता है कि यह सिर्फ चेतावनी है: आप शायद कुछ गलत कर रहे हैं, लेकिन यह सही भी हो सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.