नया मान पढ़ने के बाद बासी मूल्य पढ़ना [डुप्लिकेट]

Nov 24 2020

इस उदाहरण पर विचार करें। ले रहे थे:

int var = 0;

धागा एक:

System.out.println(var);
System.out.println(var);

धागा बी:

var = 1;

धागे समवर्ती रूप से चलते हैं। क्या निम्नलिखित आउटपुट संभव है?

1
0

अर्थात्, नया मान पढ़ने के बाद मूल मान पढ़ा जाता है। varअस्थिर नहीं है। मेरी आंत की भावना यह संभव नहीं है।

जवाब

3 Eugene Nov 26 2020 at 11:41

आप System.out.printlnआंतरिक रूप से ऐसा कर रहे हैं synchronized(this) {...}जो चीजों को थोड़ा और बदतर बना देगा। लेकिन उसके साथ भी, आपका पाठक धागा अभी भी देख सकता है 1, 0, अर्थात: एक पाठ पढ़ा जाता है।

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

JLS कहता है कि:

यदि x और y एक ही थ्रेड के कार्य हैं और x प्रोग्राम क्रम में y से पहले आता है, तो hb (x, y)।

चूंकि दोनों में varहैं program order, हम आकर्षित कर सकते हैं:

                (po) 
firstRead(var) ------> secondRead(var)
// po == program order

वह वाक्य यह भी कहता है कि यह एक happens-beforeआदेश बनाता है , इसलिए:

                (hb) 
firstRead(var) ------> secondRead(var)
// hb == happens before

लेकिन वह "एक ही धागे" के भीतर है। यदि हम कई थ्रेड्स के बारे में तर्क करना चाहते हैं, तो हमें सिंक्रोनाइज़ेशन ऑर्डर देखने की जरूरत है । हमें इसकी आवश्यकता है क्योंकि उसी अनुच्छेद के बारे में happens-before orderकहते हैं:

यदि एक एक्शन x सिंक्रोनाइज़ करता है- एक एक्शन वाई के साथ, तो हमारे पास hb (x, y) भी है।

इसलिए यदि हम इस श्रृंखला का निर्माण करते हैं program orderऔर synchronizes-with order, हम परिणाम के बारे में तर्क कर सकते हैं। आइए इसे अपने कोड पर लागू करें:

            (NO SW)                    (hb)
write(var) ---------> firstRead(var) -------> secondRead(var)

// NO SW == there is "no synchronizes-with order" here
// hb    == happens-before

और यह वह जगह है जहाँ happens-before consistencyएक ही अध्याय में खेलते हैं :

क्रियाओं का एक समूह A होता है-सुसंगत होने से पहले यदि सभी A, r (W) में पढ़ते हैं, जिसमें r (r) है, जो r द्वारा देखी गई लेखन क्रिया है, तो ऐसा नहीं है कि hb (r, W (r)) या वहां A में एक राइट w मौजूद है जैसे wv = rv और hb (W (r), w) और hb (w, r)।

क्रियाओं के लगातार-सेट होने से पहले, प्रत्येक रीड एक ऐसा लेखन देखता है जिसे ऑर्डर करने से पहले देखने की अनुमति है

मैं स्वीकार करता हूं कि मैं पहले वाक्य को बहुत अस्पष्ट रूप से समझता हूं और यहीं पर एलेक्सी ने मेरी सबसे अधिक मदद की है, क्योंकि वह इसे कहते हैं:

या तो पिछले लिखने कि में हुआ देखने के पढ़ने happens-beforeया किसी अन्य लिखने

क्योंकि वहाँ कोई नहीं synchronizes-with orderहै, और स्पष्ट रूप से वहाँ नहीं है happens-before order, पढ़ने के धागे को एक दौड़ के माध्यम से पढ़ने की अनुमति है। और इस प्रकार 1, से 0


जैसे ही आप एक सही परिचय देते हैं synchronizes-with order, उदाहरण के लिए यहाँ से

मॉनिटर पर एक अनलॉक क्रिया m सिंक्रोनाइज़ करता है-पर बाद की सभी लॉक क्रियाओं के साथ ...

एक अस्थिर चर v के लिए एक लिखता है-किसी भी धागे द्वारा v के बाद के सभी पाठों के साथ ...

ग्राफ़ बदलता है (मान लें कि आपने बनाने के लिए चुना है var volatile):

               SW                       PO
write(var) ---------> firstRead(var) -------> secondRead(var)

// SW == there IS "synchronizes-with order" here
// PO == happens-before

PO(प्रोग्राम ऑर्डर) HBजेएलएस से इस उत्तर में उद्धृत पहले वाक्य के माध्यम से (पहले होता है ) देता है । और SWदेता है HBक्योंकि:

यदि कोई एक्शन x सिंक्रोनाइज़ करता है-एक एक्शन वाई के साथ, तो हमारे पास hb (x, y) भी है।

जैसे की:

               HB                       HB
write(var) ---------> firstRead(var) -------> secondRead(var)

और अब happens-before orderकहता है कि रीडिंग थ्रेड उस मान को पढ़ेगा जो "अंतिम एचबी में लिखा गया था", या इसका मतलब है कि पढ़ना 1तब 0असंभव है।


मैंने उदाहरण के लिए jcstress नमूने लिए और एक छोटा सा परिवर्तन पेश किया (जैसे आपका System.out.printlnकाम करता है):

@JCStressTest
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "Doing both reads early.")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "Doing both reads late.")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "Doing first read early, not surprising.")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "First read seen racy value early, and the second one did not.")
@State
public class SO64983578 {

    private final Holder h1 = new Holder();
    private final Holder h2 = h1;

    private static class Holder {

        int a;
        int trap;
    }

    @Actor
    public void actor1() {
        h1.a = 1;
    }

    @Actor
    public void actor2(II_Result r) {
        Holder h1 = this.h1;
        Holder h2 = this.h2;
        
        h1.trap = 0;
        h2.trap = 0;

        synchronized (this) {
            r.r1 = h1.a;
        }

        synchronized (this) {
            r.r2 = h2.a;
        }

    }

}

ध्यान दें synchronized(this){....}कि प्रारंभिक उदाहरण का हिस्सा नहीं है। सिंक्रनाइज़ेशन के साथ भी, मैं अभी भी 1, 0परिणाम के रूप में देख सकता हूं । यह सिर्फ साबित होता है कि यहां तक कि के साथ है synchronized(कि से आंतरिक रूप से आता है System.out.println), तब भी आप प्राप्त कर सकते हैं 1की तुलना में 0

1 akuzminykh Nov 24 2020 at 17:04

जब मूल्य varपढ़ा जाता है और यह 1वापस नहीं बदलेगा। यह आउटपुट न तो दृश्यता के कारण हो सकता है और न ही रीऑर्डरिंग के कारण। क्या हो सकता है 0 0, 0 1और 1 1

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

इसके अलावा, इस बात की कोई गारंटी नहीं है कि लेखन बिल्कुल दिखाई देगा, जैसा varकि न तो चिह्नित किया गया है volatileऔर न ही लेखन किसी भी तरह से सिंक्रनाइज़ किया गया है।

AlexRevetchi Nov 26 2020 at 15:32

मुझे लगता है कि यहां जो कुछ याद आ रहा है वह यह है कि वे धागे वास्तविक भौतिक कोर पर चलते हैं और हमारे यहां कुछ संभावित संस्करण हैं:

  1. सभी धागे एक ही कोर पर चलते हैं, फिर समस्या उन 3 निर्देशों के निष्पादन के आदेश तक कम हो जाती है, इस मामले में 1,0 संभव नहीं है मुझे लगता है, प्रिंटलैन निष्पादन को सिंक्रनाइज़ेशन द्वारा बनाई गई मेमोरी बाधाओं के कारण आदेश दिया जाता है, ताकि 1,0 को छोड़कर

  2. ए और बी 2 अलग-अलग कोर पर चलता है, फिर 1,0 भी संभव नहीं दिखता है, जैसे ही कोर जो कि थ्रेड ए पढ़ता है 1 चलाता है, कोई ऐसा तरीका नहीं है जिसे 0 के बाद पढ़ा जाएगा, जैसे कि ऊपर दिए गए प्रिंटल का आदेश दिया गया है।

  3. थ्रेड ए को उन 2 प्रिंटलनों के बीच में पुनर्निर्धारित किया जाता है, इसलिए दूसरे प्रिंटलोन को एक अलग कोर पर निष्पादित किया जाता है, या तो बी के रूप में एक ही निष्पादित किया जाता है या एक अलग 3 कोर पर निष्पादित किया जाएगा। इसलिए जब 2 प्रिंटलॉनों को अलग-अलग कोर पर निष्पादित किया जाता है, तो यह निर्भर करता है कि 2 कोर किस मान को देखते हैं, यदि var सिंक्रनाइज़ नहीं है (यह स्पष्ट नहीं है कि यह var सदस्य है), तो वे 2 कोर अलग-अलग var मान देख सकते हैं, इसलिए 1,0 के लिए एक संभावना है।

तो यह एक कैश सुसंगत समस्या है।

PS मैं एक jvm विशेषज्ञ नहीं हूं, इसलिए यहां खेलने में अन्य चीजें हो सकती हैं।

FrancescoMenzani Nov 25 2020 at 01:35

अन्य उत्तरों में जोड़ना:

साथ longऔर double, लिखती है ताकि पहले 32 बिट पिछले 32 बिट, या viceversa से पहले दिखाई देने लगते हैं सकता है परमाणु नहीं हो सकता। इसलिए पूरी तरह से विभिन्न मूल्य आउटपुट हो सकते हैं।