2016-02-22 7 views
7

प्रभावी जावा में:पढ़ना एक और धागे से साझा चर (प्रभावी जावा # 66)

// Broken! - How long would you expect this program to run 
class StopThread { 
    private static boolean stopRequested = false; 

    public static void main(String[] args) 
      throws InterruptedException { 

     Thread backgroundThread = new Thread(new Runnable() { 
      public void run() { 
       int i = 0; 
       while (!stopRequested) { 
        i++; 
       } 
      } 
     }); 
     backgroundThread.start(); 

     TimeUnit.SECONDS.sleep(1); 
     stopRequested = true; 
    } 
} 

के रूप में यहोशू बलोच ने कहा, इस कार्यक्रम समाप्त नहीं होगा: आइटम 66, यहोशू बलोच जीवन विफलता के बारे में उदाहरण दिया । लेकिन, अगर मैं i++ को System.out.println(i++) में बदलता हूं, तो यह सफलतापूर्वक समाप्त हो जाता है!

मैं यह नहीं समझ सकता कि यह कैसा होता है!

+0

उत्तरों के स्वतंत्र, आपको यह भी विचार करने की आवश्यकता है कि पुस्तक 10 साल और 3 प्रमुख संस्करण पुरानी है। चीजें बदलती हैं, कभी-कभी। –

+0

@ सेनपैट्रिकफ्लॉइड कभी-कभी वे नहीं करते हैं, हालांकि: आइटम में मौजूद व्यवहार अभी भी होता है। –

+0

@ एंडी टर्नर मुझे पता है। यही कारण है कि मैंने इसे टिप्पणी के रूप में प्रदान किया, जवाब के रूप में –

उत्तर

5

समस्या stopRequest के मेमोरी मान से संबंधित है।

यह चर volatile के रूप में परिभाषित नहीं है।

यदि आपके पास दो प्रोसेसर हैं तो आंतरिक थ्रेड stopRequest के मूल्य को इसकी रजिस्ट्री से लिया गया है।

मुख्य धागा अन्य प्रोसेसर की रजिस्ट्री में stopRequest के मान को परिवर्तित करता है।

तो मुख्य धागा stopRequest के मान को संशोधित करता है लेकिन धागा केवल इसकी एक प्रति को देखता है जो कभी नहीं बदलता है।

के बाद संशोधित (ΔλЛ की सौंपना धन्यवाद) PrintStream के स्रोत कोड पर एक नज़र डालें: एक System.out.print आदेश का उपयोग करते हुए इसे इस को भेजे गए मान मुद्रित करने के लिए एक स्पष्ट synchronized ब्लॉक का उपयोग करेगा अनुदान देगा का मूल्य है कि stopRequest मुख्य स्मृति से लिया गया है और प्रोसेसर की रजिस्ट्री से नहीं।

volatile कीवर्ड जोड़ना जेवीएम को प्रोसेसर के रजिस्ट्रियों के बजाय मुख्य मेमोरी से मूल्य लेने के लिए सूचित करेगा और इससे समस्या हल हो जाएगी।

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

मेमोरी मॉडल volatile (मुख्य धागा उपयोग प्रोसेसर 1 और स्पष्ट धागा उपयोग प्रोसेसर 2)

Processor 1   Processor 2  Main memory 
-----------   -----------  ----------- 
    false    false   false 
    true     false   true  // After setting 
                //stopRequest to true 

stopRequest परिभाषित volatile के रूप में जहां सभी धागे मुख्य स्मृति से stopRequest पढ़ बिना।

Processor 1   Processor 2  Main memory 
-----------   -----------  ----------- 
     NA     NA   false 
     NA     NA   true  // After setting 
                //stopRequest to true 
+0

'परमाणु बूलियन' का उपयोग करने के लिए एक अच्छी जगह हो सकती है – flakes

+1

@flkes: इसके लिए बाधा का उपयोग करना बेहतर होगा, और मौजूदा फ्लैग को डुप्लिकेट करने वाला ध्वज नहीं बनाना बेहतर होगा। उल्लेख करना चाहिए कि println एक ताला प्राप्त करता है और यह चर के अद्यतन मूल्य को दृश्यमान होने के लिए मजबूर करता है। –

+3

हाँ .... लेकिन मुझे लगता है कि यहोशू के प्रयासों को समझाया गया था कि एक सूक्ष्म त्रुटि भी बहुत अजीब त्रुटियां पैदा कर सकती है, लेकिन निश्चित रूप से एक परमाणु बूलियन इस समस्या को हल करेगा। –

1

tl; डॉ: यह सबसे अधिक संभावना println का एक "आकस्मिक" पक्ष प्रभाव सिंक्रनाइज़ हो रहा है।

सबसे पहले, यह समझें कि यह मामला नहीं है कि थ्रेड को समाप्त करने के लिए गारंटी नहीं है; यह है कि यह गारंटी नहीं है कि यह खत्म होगा। दूसरे शब्दों में, stopRequested पर दौड़ की स्थिति - इस तथ्य के कारण कि एक धागा इसे लिख रहा है, और दूसरा धागा इसे पढ़ रहा है, और दो धागे के बीच कोई सिंक्रनाइज़ेशन नहीं है - इसका मतलब है कि JVM हो सकता है, लेकिन यह नहीं है पाठक को यह देखने की ज़रूरत है कि लेखक ने क्या किया है।

तो, System.out.println क्यों बदलता है? क्योंकि यह एक सिंक्रनाइज़ विधि है। यह वास्तव में आपको stopRequested के बारे में कोई गारंटी नहीं देता है, जहां तक ​​जेएलएस का संबंध है, लेकिन इसका मतलब यह है कि जेवीएम को कुछ चीजें करना है, जैसे कि (एन असंबद्ध) लॉक प्राप्त करना, और स्थापित करना (असंबंधित) होता है-किनारों से पहले , जो इसे अधिक संभावना बनाता है कि stopRequested पर लिखने के धागे में देखा जाएगा।

1

चूंकि आप धागे को नहीं बता रहे हैं कि stopRequested एक मान है जिसे उस थ्रेड के बाहर से संशोधित किया जा सकता है, इस बात की कोई गारंटी नहीं है कि while चर के नवीनतम मूल्य का मूल्यांकन करेगा।

यही कारण है कि volatile कीवर्ड इस स्थिति में उपयोगी है, क्योंकि यह स्पष्ट रूप से stopRequested मान को लागू करेगा, जब पढ़ा जाएगा, तो किसी भी धागे द्वारा सेट किया गया सबसे हालिया मूल्य होगा।

वास्तव में धागे के दृष्टिकोण से stopRequested एक लूप इनवेरिएंट है, क्योंकि यह कभी भी पढ़ा नहीं जाता है, इसलिए ऑप्टिमाइज़ेशन विकल्पों को भी माना जाना चाहिए: यदि कोई मान माना जाता है कि कोई मान नहीं माना जाता है संशोधित किया गया है तो प्रत्येक पुनरावृत्ति पर इसका मूल्यांकन करने का कोई कारण नहीं है।

+0

हालांकि यह इस संदर्भ में 'System.out.println()' के प्रभाव की व्याख्या नहीं करता है। – syntagma

+0

'System.out.println()' का प्रभाव कुछ आईएमएचओ नहीं है, कुछ मशीनों पर यह कुछ अन्य लोगों पर समस्या का समाधान कर सकता है, इसलिए यह समस्या को समझने के लिए अप्रासंगिक है क्योंकि समस्या कहीं और है। सीपीयू पर थ्रेड आवंटित किए जाने के अनुसार, जो buffered आउटपुट और ऐसी चीजों का ख्याल रखता है। यह एक तरह का अपरिभाषित व्यवहार है। – Jack

+0

@ जैक मेरा प्रश्न है: क्यों 'System.out.println() 'प्रोग्राम की स्थिति बदलता है, यहां तक ​​कि' सिंक्रनाइज़ 'और' अस्थिर 'कुंजी शब्द का उपयोग नहीं करते हैं। –

0

System.out.println() एक संसाधन कंसोल पर लिखने के लिए, अपने आप में एक अवरुद्ध विधि ... जिसका अर्थ है, यह backgroundThread()print() को कंसोल पर अवरुद्ध कर देगा जो अनुरोध करता है। यह इसके लिए एक बाधा भेजने के समान है।

इस प्रकार, backgroundThread() बूलियन के मूल्य में परिवर्तन के बारे में जागरूक हो जाएगा और निष्पादन को रोक देगा, जिससे डेमन को समाप्त किया जा सकेगा।

संबंधित मुद्दे