धागे के बीच स्थिर चर साझा किए जाते हैं?


95

थ्रेडिंग पर एक उच्च स्तरीय जावा वर्ग में मेरे शिक्षक ने कुछ ऐसा कहा जो मुझे यकीन नहीं था।

उन्होंने कहा कि निम्नलिखित कोड आवश्यक रूप से अद्यतन नहीं करेगा ready चर को । उनके अनुसार, दो धागे जरूरी स्थिर चर को साझा नहीं करते हैं, विशेष रूप से उस स्थिति में जब प्रत्येक थ्रेड (मुख्य धागा बनाम ReaderThread) अपने स्वयं के प्रोसेसर पर चल रहा है और इसलिए एक ही रजिस्टर / कैश / आदि और एक सीपीयू को साझा नहीं करता है। दूसरे को अपडेट नहीं करेंगे।

अनिवार्य रूप से, उन्होंने कहा कि यह संभव है ready मुख्य धागे में अपडेट किया गया है, लेकिन अंदर नहीं है ReaderThread, ताकि ReaderThreadअसीम रूप से लूप हो जाए ।

उन्होंने यह भी दावा किया कि कार्यक्रम को प्रिंट करना 0या करना संभव था 42। मैं समझता हूं कैसे42 मुद्रित किया जा सकता है, लेकिन नहीं 0। उन्होंने उल्लेख किया कि यह तब होगा जब numberचर डिफ़ॉल्ट मान पर सेट हो।

मुझे लगा कि शायद इसकी गारंटी नहीं है कि थ्रेड्स के बीच स्टेटिक वैरिएबल अपडेट किया गया है, लेकिन यह मुझे जावा के लिए बहुत ही अजीब लगता है। बनाता हैready अस्थिर इस समस्या को सही है?

उसने यह कोड दिखाया:

public class NoVisibility {  
    private static boolean ready;  
    private static int number;  
    private static class ReaderThread extends Thread {   
        public void run() {  
            while (!ready)   Thread.yield();  
            System.out.println(number);  
        }  
    }  
    public static void main(String[] args) {  
        new ReaderThread().start();  
        number = 42;  
        ready = true;  
    }  
}

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

1
अपने शिक्षक से पूछें कि वह किस तरह का आर्किटेक्चर रखता है, यह '0' देखना संभव होगा। फिर भी, सिद्धांत पर वह सही है।
सर्वश्रेष्ठ

4
@bestsss यह पूछने पर कि इस तरह के प्रश्न से शिक्षक को पता चल जाएगा कि वह जो कह रहा था, उसके पूरे बिंदु को याद नहीं किया। मुद्दा यह है कि सक्षम प्रोग्रामर समझते हैं कि क्या गारंटी है और क्या नहीं है और उन चीजों पर भरोसा नहीं करते हैं जिनकी गारंटी नहीं है, कम से कम यह समझने के बिना कि क्या गारंटी नहीं है और क्यों नहीं।
डेविड श्वार्ट्ज

उन्हें एक ही क्लास लोडर द्वारा लोड की गई हर चीज के बीच साझा किया जाता है। धागे सहित।
लोर्न

आपका शिक्षक (और स्वीकृत उत्तर) 100% सही है, लेकिन मैं यह उल्लेख करूंगा कि यह शायद ही कभी होता है - यह एक ऐसी समस्या है जो सालों तक छिपी रहेगी और केवल तभी प्रदर्शित होगी जब यह सबसे अधिक हानिकारक होगी। यहां तक ​​कि समस्या को उजागर करने की कोशिश करने वाले लघु परीक्षण भी कार्य करते हैं, हालांकि सब कुछ ठीक है (संभवतः क्योंकि उनके पास जेवीएम के लिए बहुत अधिक अनुकूलन करने का समय नहीं है), इसलिए इसके बारे में पता होना वास्तव में एक अच्छा मुद्दा है।
बिल के

जवाबों:


75

जब दृश्यता की बात आती है तो स्थैतिक चर के बारे में कुछ खास नहीं होता है। यदि वे सुलभ हैं तो कोई भी धागा उन पर प्राप्त कर सकते हैं, इसलिए आप अधिक संगामिति की समस्याएँ देख सकते हैं क्योंकि वे अधिक उजागर हैं।

जेवीएम के मेमोरी मॉडल द्वारा लगाया गया दृश्यता मुद्दा है। यहां मेमोरी मॉडल के बारे में बात करने वाला एक लेख है और थ्रेड्स के लिए दृश्यमान कैसे लिखा जाता है । आप उन बदलावों पर भरोसा नहीं कर सकते हैं जो एक धागे को समय पर अन्य थ्रेड्स के लिए दृश्यमान बनाता है (वास्तव में JVM का कोई दायित्व नहीं है कि आप उन परिवर्तनों को बिल्कुल भी, किसी भी समय सीमा में देखें), जब तक कि आप एक होने से पहले संबंध स्थापित न कर लें। ।

यहाँ उस लिंक से एक उद्धरण (जेड वेस्ली-स्मिथ द्वारा टिप्पणी में आपूर्ति की गई है):

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

  • एक थ्रेड में प्रत्येक क्रिया होती है-उस थ्रेड में प्रत्येक क्रिया से पहले जो प्रोग्राम के क्रम में बाद में आती है।

  • मॉनिटर का एक अनलॉक (सिंक्रोनाइज़्ड ब्लॉक या मेथड एग्जिट) उसी मॉनिटर के हर बाद के लॉक (सिंक्रोनाइज़्ड ब्लॉक या मेथड एंट्री) से पहले होता है। और क्योंकि होने से पहले संबंध सकर्मक होता है, किसी सूत्र को खोलने से पहले किसी थ्रेड की सभी क्रियाएँ-किसी भी थ्रेड को लॉक करने से पहले की जाने वाली सभी क्रियाओं की निगरानी करता है।

  • एक अस्थिर क्षेत्र के लिए एक लेखन होता है-उसी क्षेत्र के प्रत्येक बाद के पढ़ने से पहले। वाष्पशील क्षेत्रों के लेखन और रीड में समान स्मृति स्थिरता प्रभाव होते हैं जैसे मॉनिटर में प्रवेश करना और बाहर निकलना, लेकिन पारस्परिक बहिष्कार लॉकिंग में प्रवेश नहीं करते हैं।

  • किसी थ्रेड पर प्रारंभ करने के लिए कॉल होता है-प्रारंभ किए गए थ्रेड में किसी भी क्रिया से पहले।

  • किसी थ्रेड में सभी क्रियाएं होती हैं-इससे पहले कि कोई अन्य थ्रेड सफलतापूर्वक उस थ्रेड पर शामिल होने से वापस आ जाए।


3
व्यवहार में, "समयबद्ध तरीके से" और "कभी" पर्यायवाची हैं। उपरोक्त कोड को कभी भी समाप्त नहीं करना बहुत संभव होगा।
ट्रे 0

4
इसके अलावा, यह एक और विरोधी पैटर्न प्रदर्शित करता है। साझा स्थिति के एक से अधिक टुकड़े की सुरक्षा के लिए अस्थिरता का उपयोग न करें। यहां, संख्या और तैयार दो टुकड़े हैं, और उन्हें लगातार दोनों को अपडेट / पढ़ने के लिए, आपको वास्तविक सिंक्रनाइज़ेशन की आवश्यकता है।
TREE

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

2
"जब तक आप अस्थिर कीवर्ड का उपयोग नहीं करते हैं या सिंक्रनाइज़ नहीं करते हैं।" "तब तक पढ़ना चाहिए जब तक कि लेखक और पाठक के बीच कोई प्रासंगिक संबंध नहीं होता है" और यह लिंक: download.oracle.com/javase/6/docs/api/java/util/concurrent/…
जेड वेस्ले-स्मिथ

2
@bestsss अच्छी तरह से देखा गया। दुर्भाग्य से थ्रेडग्रुप इतने तरीकों से टूट गया है।
जेड वेसले-स्मिथ

37

वह दृश्यता के बारे में बात कर रहा था और इसे सचमुच में नहीं लिया जाना था।

स्थैतिक चर वास्तव में थ्रेड्स के बीच साझा किए जाते हैं, लेकिन एक थ्रेड में किए गए परिवर्तन तुरंत दूसरे थ्रेड को दिखाई नहीं दे सकते हैं, जिससे ऐसा लगता है कि चर की दो प्रतियां हैं।

यह आलेख एक दृश्य प्रस्तुत करता है जो इस बात के अनुरूप है कि उसने कैसे जानकारी प्रस्तुत की:

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

  • जावा में प्रत्येक धागा एक अलग मेमोरी स्पेस में होता है (यह स्पष्ट रूप से असत्य है, इसलिए इस पर मेरे साथ रहें)।

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

  • मेमोरी लिखती है कि एक थ्रेड में "लीक थ्रू" हो सकता है और दूसरे थ्रेड द्वारा देखा जा सकता है, लेकिन इसकी कोई गारंटी नहीं है। सुस्पष्ट संचार के बिना, आप गारंटी नहीं दे सकते कि कौन से लेखन को अन्य थ्रेड्स द्वारा देखा जाता है, या यहां तक ​​कि जिस क्रम में उन्हें देखा जाता है।

...

धागा मॉडल

लेकिन फिर, यह केवल थ्रेडिंग और अस्थिरता के बारे में सोचने के लिए एक मानसिक मॉडल है, शाब्दिक रूप से नहीं कि जेवीएम कैसे काम करता है।


12

मूल रूप से यह सच है, लेकिन वास्तव में समस्या अधिक जटिल है। साझा डेटा की दृश्यता न केवल सीपीयू कैश से, बल्कि निर्देशों के आउट-ऑफ-ऑर्डर निष्पादन से भी प्रभावित हो सकती है।

इसलिए जावा एक मेमोरी मॉडल को परिभाषित करता है , जो बताता है कि किन परिस्थितियों में थ्रेड्स साझा डेटा की सुसंगत स्थिति देख सकते हैं।

आपके विशेष मामले में, volatileदृश्यता की गारंटी देता है।


8

वे इस अर्थ में "साझा" हैं कि वे दोनों एक ही चर को संदर्भित करते हैं, लेकिन वे जरूरी नहीं कि एक दूसरे के अपडेट को देखते हैं। यह किसी भी चर के लिए सच है, न कि केवल स्थिर।

और सिद्धांत रूप में, किसी अन्य थ्रेड द्वारा किए गए लेखन एक अलग क्रम में दिखाई दे सकते हैं, जब तक कि चर घोषित नहीं किए जाते हैं volatileया राइट स्पष्ट रूप से सिंक्रनाइज़ नहीं किए जाते हैं।


4

एक एकल वर्ग लोडर के भीतर, स्थिर क्षेत्र हमेशा साझा किए जाते हैं। थ्रेड्स में डेटा को स्पष्ट रूप से करने के लिए, आप एक सुविधा का उपयोग करना चाहेंगे ThreadLocal


2

जब आप स्थैतिक आदिम प्रकार चर जावा डिफ़ॉल्ट शुरू में स्थिर चर के लिए एक मान प्रदान करता है

public static int i ;

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


-2

@dontocsata आप अपने शिक्षक पर वापस जा सकते हैं और उसे थोड़ा स्कूल कर सकते हैं :)

वास्तविक दुनिया से कुछ नोट्स और आप जो भी देखते हैं या कहे जाते हैं। कृपया ध्यान दें, नीचे दिए गए शब्द इस विशेष मामले से संबंधित सटीक क्रम में दिखाए गए हैं।

निम्नलिखित 2 चर समान रूप से किसी भी आर्किटेक्चर के तहत एक ही कैश लाइन पर रहेंगे।

private static boolean ready;  
private static int number;  

Thread.exit(मुख्य धागा) बाहर निकलने exitकी गारंटी है और थ्रेड ग्रुप थ्रेड हटाने (और कई अन्य मुद्दों) के कारण मेमोरी बाड़ के कारण होने की गारंटी है। (यह एक समन्वित कॉल है, और मुझे कोई एकल तरीका नहीं है जिसे डब्ल्यू / ओ सिंक भाग में लागू किया जा सकता है क्योंकि थ्रेडग्रुप को समाप्त करना होगा साथ ही अगर कोई डेमन थ्रेड्स शेष नहीं हैं, तो)।

शुरू किया गया धागा ReaderThreadइस प्रक्रिया को जीवित रखने वाला है क्योंकि यह एक डेमॉन नहीं है! इस प्रकार एक साथ (या यदि संदर्भ स्विच होने से पहले संख्या) एक साथ फ़्लश किया जाएगा readyऔर numberइस मामले में पुन: व्यवस्थित करने का कोई वास्तविक कारण नहीं है तो कम से कम मैं एक के बारे में सोच भी नहीं सकता। तुम कुछ भी लेकिन देखने के लिए कुछ वास्तव में अजीब की आवश्यकता होगी 42। फिर से मुझे लगता है कि दोनों स्थिर चर एक ही कैश लाइन में होंगे। मैं सिर्फ एक कैश लाइन 4 बाइट्स या JVM की कल्पना नहीं कर सकता जो उन्हें एक निरंतर क्षेत्र (कैश लाइन) में असाइन नहीं करेगा।


3
@bestsss जबकि यह सब आज सच है, यह कार्यक्रम के शब्दार्थों पर नहीं, बल्कि इसकी सच्चाई के लिए वर्तमान जेवीएम कार्यान्वयन और हार्डवेयर वास्तुकला पर निर्भर करता है। इसका मतलब है कि यह कार्यक्रम अभी भी टूटा हुआ है, भले ही यह काम कर सकता है। इस उदाहरण का एक तुच्छ संस्करण खोजना आसान है जो वास्तव में निर्दिष्ट तरीकों से विफल होता है।
जेड वेसले-स्मिथ

1
मैंने कहा था कि यह कल्पना का पालन नहीं करता है, हालांकि एक शिक्षक के रूप में कम से कम एक उपयुक्त उदाहरण मिलता है जो वास्तव में कुछ कमोडिटी आर्किटेक्चर पर विफल हो सकता है, इसलिए उदाहरण वास्तविक की तरह है।
bestsss

6
संभवतः थ्रेड-सेफ कोड लिखने की सबसे बुरी सलाह मैंने देखी है।
लॉरेंस डॉल

4
@Bestsss: आपके प्रश्न का सरल उत्तर केवल यह है: "विनिर्देश और दस्तावेज़ में कोड, आपके विशेष सिस्टम या कार्यान्वयन के साइड-इफेक्ट्स के लिए नहीं"। यह अंतर्निहित हार्डवेयर के अज्ञेय के लिए डिज़ाइन किए गए एक वर्चुअल-मशीन प्लेटफॉर्म में विशेष रूप से महत्वपूर्ण है।
लॉरेंस डॉल

1
@Bestsss: शिक्षकों का कहना है कि (ए) कोड का परीक्षण करने पर आप अच्छी तरह से काम कर सकते हैं और (बी) कोड टूट गया है क्योंकि यह हार्डवेयर ऑपरेशन पर निर्भर करता है न कि ऐनक की गारंटी पर। मुद्दा यह है कि ऐसा लगता है कि यह ठीक है, लेकिन यह ठीक नहीं है।
लॉरेंस डॉल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.