CodeGym /Java Blog /अनियमित /धागे का प्रबंधन। अस्थिर कीवर्ड और उपज () विधि
John Squirrels
स्तर 41
San Francisco

धागे का प्रबंधन। अस्थिर कीवर्ड और उपज () विधि

अनियमित ग्रुप में प्रकाशित
नमस्ते! हम मल्टीथ्रेडिंग का अपना अध्ययन जारी रखते हैं। volatileआज हम कीवर्ड और yield()विधि के बारे में जानेंगे । चलो गोता लगाएँ :)

अस्थिर कीवर्ड

मल्टीथ्रेडेड एप्लिकेशन बनाते समय, हम दो गंभीर समस्याओं में भाग सकते हैं। सबसे पहले, जब एक मल्टीथ्रेडेड एप्लिकेशन चल रहा होता है, तो विभिन्न थ्रेड वेरिएबल्स के मूल्यों को कैश कर सकते हैं (हम पहले ही इस बारे में बात कर चुके हैं 'अस्थिर का उपयोग' शीर्षक वाले पाठ में )। आपके पास ऐसी स्थिति हो सकती है जहां एक धागा एक चर के मान को बदलता है, लेकिन दूसरा धागा परिवर्तन नहीं देखता है, क्योंकि यह चर की कैश की गई प्रति के साथ काम कर रहा है। स्वाभाविक रूप से, परिणाम गंभीर हो सकते हैं। मान लीजिए कि यह केवल कोई पुराना चर नहीं है, बल्कि आपके बैंक खाते की शेष राशि है, जो अचानक बेतरतीब ढंग से ऊपर और नीचे कूदना शुरू कर देती है :) यह मज़ेदार नहीं लगता है, है ना? दूसरा, जावा में, सभी आदिम प्रकारों को पढ़ने और लिखने के लिए संचालन,longdouble, परमाणु हैं। ठीक है, उदाहरण के लिए, यदि आप एक थ्रेड पर एक चर के मान को बदलते हैं int, और दूसरे थ्रेड पर आप चर के मान को पढ़ते हैं, तो आपको या तो उसका पुराना मान मिलेगा या नया, यानी वह मान जो परिवर्तन के परिणामस्वरूप हुआ थ्रेड 1 में। कोई 'मध्यवर्ती मान' नहीं हैं। longहालांकि, यह एस और doubleएस के साथ काम नहीं करता है । क्यों? क्रॉस-प्लेटफ़ॉर्म समर्थन के कारण। शुरुआती स्तरों पर याद रखें कि हमने कहा था कि जावा का मार्गदर्शक सिद्धांत 'एक बार लिखो, कहीं भी भागो' है? इसका मतलब क्रॉस-प्लेटफॉर्म सपोर्ट है। दूसरे शब्दों में, जावा एप्लिकेशन सभी प्रकार के विभिन्न प्लेटफॉर्म पर चलता है। उदाहरण के लिए, Windows ऑपरेटिंग सिस्टम पर, Linux या MacOS के विभिन्न संस्करण। यह उन सभी पर बिना किसी रोक-टोक के चलेगा। 64 बिट्स में वजनी,longdoubleजावा में 'सबसे भारी' आदिम हैं। और कुछ 32-बिट प्लेटफॉर्म केवल 64-बिट चर के परमाणु पढ़ने और लिखने को लागू नहीं करते हैं। ऐसे वेरिएबल्स को दो ऑपरेशन में पढ़ा और लिखा जाता है। सबसे पहले, पहले 32 बिट्स को वेरिएबल में लिखा जाता है, और फिर अन्य 32 बिट्स को लिखा जाता है। नतीजतन, एक समस्या उत्पन्न हो सकती है। एक थ्रेड एक Xचर के लिए कुछ 64-बिट मान लिखता है और ऐसा दो ऑपरेशनों में करता है। उसी समय, एक दूसरा थ्रेड वेरिएबल के मान को पढ़ने की कोशिश करता है और ऐसा उन दो ऑपरेशनों के बीच करता है - जब पहले 32 बिट्स लिखे गए हैं, लेकिन दूसरे 32 बिट्स नहीं लिखे गए हैं। नतीजतन, यह एक मध्यवर्ती, गलत मान पढ़ता है, और हमारे पास एक बग है। उदाहरण के लिए, यदि ऐसे प्लेटफॉर्म पर हम संख्या को 9223372036854775809 पर लिखने का प्रयास करते हैं एक चर के लिए, यह 64 बिट्स पर कब्जा कर लेगा। द्विआधारी रूप में, यह इस तरह दिखता है: 100000000000000000000000000000000000000000000000000000000000001 पहला धागा चर को संख्या लिखना शुरू करता है। सबसे पहले, यह पहले 32 बिट्स (1000000000000000000000000000000) लिखता है और फिर दूसरा 32 बिट्स (0000000000000000000000000000001) लिखता है और दूसरा धागा चर के मध्यवर्ती मान (10000000000000000000000000000000) को पढ़ते हुए इन परिचालनों के बीच फंस सकता है, जो पहले 32 बिट्स हैं जो पहले ही लिखे जा चुके हैं। दशमलव प्रणाली में, यह संख्या 2,147,483,648 है। दूसरे शब्दों में, हम सिर्फ एक चर के लिए संख्या 9223372036854775809 लिखना चाहते थे, लेकिन इस तथ्य के कारण कि यह ऑपरेशन कुछ प्लेटफार्मों पर परमाणु नहीं है, हमारे पास बुराई संख्या 2,147,483,648 है, जो कहीं से भी निकली और इसका अज्ञात प्रभाव होगा कार्यक्रम। दूसरे थ्रेड ने लिखे जाने से पहले केवल वेरिएबल के मान को पढ़ा, यानी थ्रेड ने पहले 32 बिट्स को देखा, लेकिन दूसरे 32 बिट्स को नहीं। बेशक, ये समस्याएं कल नहीं उठीं। जावा उन्हें एक ही कीवर्ड से हल करता है: volatile. अगर हम इस्तेमाल करते हैंvolatileहमारे कार्यक्रम में कुछ चर घोषित करते समय कीवर्ड…

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…यह मतलब है कि:
  1. यह हमेशा परमाणु रूप से पढ़ा और लिखा जाएगा। भले ही यह 64-बिट doubleया long.
  2. जावा मशीन इसे कैश नहीं करेगी। तो आपके पास ऐसी स्थिति नहीं होगी जहां 10 धागे अपनी स्थानीय प्रतियों के साथ काम कर रहे हों।
इस प्रकार, दो बहुत गंभीर समस्याओं को केवल एक शब्द से हल किया जाता है :)

उपज () विधि

हम पहले ही Threadकक्षा के कई तरीकों की समीक्षा कर चुके हैं, लेकिन एक महत्वपूर्ण तरीका है जो आपके लिए नया होगा। यह yield()तरीका है । और यह वही करता है जो इसका नाम दर्शाता है! धागे का प्रबंधन।  वाष्पशील कीवर्ड और उपज () विधि - 2जब हम yieldकिसी थ्रेड पर मेथड को कॉल करते हैं, तो यह वास्तव में अन्य थ्रेड्स से बात करता है: 'अरे दोस्तों। मुझे कहीं भी जाने की कोई विशेष जल्दी नहीं है, इसलिए यदि आप में से किसी के लिए प्रोसेसर समय प्राप्त करना महत्वपूर्ण है, तो इसे लें - मैं प्रतीक्षा कर सकता हूं'। यह कैसे काम करता है इसका एक सरल उदाहरण यहां दिया गया है:

public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
हम क्रमिक रूप से तीन सूत्र बनाते और शुरू करते हैं: Thread-0, Thread-1, और Thread-2Thread-0पहले शुरू होता है और तुरंत दूसरों को देता है। फिर Thread-1शुरू होता है और उपज भी देता है। फिर Thread-2शुरू किया जाता है, जिसका फल भी मिलता है। हमारे पास कोई और धागा नहीं है, और Thread-2अंतिम स्थान पर आने के बाद, धागा अनुसूचक कहता है, 'हम्म, कोई और नया धागा नहीं है। हमारे पास कतार में कौन है? इससे पहले किसने अपनी जगह दी Thread-2? ऐसा लगता है कि यह था Thread-1। ठीक है, इसका मतलब है कि हम इसे चलने देंगे'। Thread-1अपना काम पूरा करता है और फिर थ्रेड शेड्यूलर अपना समन्वय जारी रखता है: 'ठीक है, Thread-1समाप्त। क्या हमारे पास कतार में कोई और है?'। थ्रेड-0 कतार में है: इसने ठीक पहले अपना स्थान प्राप्त कियाThread-1. अब इसकी बारी आती है और पूर्णता की ओर दौड़ती है। फिर शेड्यूलर थ्रेड्स का समन्वय करना समाप्त करता है: 'ठीक है, Thread-2आप अन्य थ्रेड्स के लिए तैयार हो गए हैं, और वे सब अब हो गए हैं। तुम सबसे आखिर में झुके थे, अब तुम्हारी बारी है।' फिर Thread-2पूरा करने के लिए दौड़ता है। कंसोल आउटपुट इस तरह दिखेगा: थ्रेड-0 दूसरों को अपनी जगह देता है थ्रेड-1 दूसरों को अपनी जगह देता है थ्रेड-2 दूसरों को अपनी जगह देता है थ्रेड-1 ने निष्पादन समाप्त कर दिया है। थ्रेड-0 का निष्पादन समाप्त हो गया है। थ्रेड-2 का निष्पादन समाप्त हो गया है। बेशक, थ्रेड शेड्यूलर थ्रेड्स को एक अलग क्रम में शुरू कर सकता है (उदाहरण के लिए, 0-1-2 के बजाय 2-1-0), लेकिन सिद्धांत समान रहता है।

होता है-नियमों से पहले

आज हम जिस आखिरी चीज को छूएंगे वह है ' पहले होता है ' की अवधारणा। जैसा कि आप पहले से ही जानते हैं, जावा में थ्रेड शेड्यूलर अपने कार्यों को करने के लिए थ्रेड्स को समय और संसाधन आवंटित करने में शामिल अधिकांश कार्य करता है। आपने यह भी बार-बार देखा है कि कैसे थ्रेड्स को एक यादृच्छिक क्रम में निष्पादित किया जाता है जो आमतौर पर भविष्यवाणी करना असंभव है। और सामान्य तौर पर, 'अनुक्रमिक' प्रोग्रामिंग के बाद हमने पहले किया था, बहुप्रचारित प्रोग्रामिंग कुछ यादृच्छिक दिखती है। आपको पहले ही विश्वास हो गया है कि आप बहुप्रचारित प्रोग्राम के प्रवाह को नियंत्रित करने के लिए कई विधियों का उपयोग कर सकते हैं। लेकिन जावा में मल्टीथ्रेडिंग का एक और स्तंभ है - 4 ' होता है-पहले ' नियम। इन नियमों को समझना काफी सरल है। कल्पना कीजिए कि हमारे पास दो सूत्र हैं - AऔरB. इनमें से प्रत्येक थ्रेड ऑपरेशन कर सकता है 1और 2. प्रत्येक नियम में, जब हम कहते हैं कि ' A होता है-B से पहले ', तो हमारा मतलब है कि Aऑपरेशन से पहले थ्रेड द्वारा किए गए सभी परिवर्तन 1और इस ऑपरेशन के परिणामस्वरूप होने वाले परिवर्तन थ्रेड को तब दिखाई देते हैं Bजब ऑपरेशन 2किया जाता है और उसके बाद। प्रत्येक नियम गारंटी देता है कि जब आप एक मल्टीथ्रेडेड प्रोग्राम लिखते हैं, तो कुछ घटनाएं 100% दूसरों से पहले घटित होंगी, और यह कि ऑपरेशन के समय 2थ्रेड हमेशा ऑपरेशन के दौरान किए गए Bपरिवर्तनों से अवगत रहेगा । आइए उनकी समीक्षा करें। A1

नियम 1।

एक ही मॉनिटर को दूसरे थ्रेड द्वारा अधिग्रहित किए जाने से पहले एक म्यूटेक्स जारी किया जाता है। मुझे लगता है कि आप यहां सब कुछ समझते हैं। यदि किसी वस्तु या वर्ग का म्यूटेक्स एक थ्रेड द्वारा अधिग्रहित किया जाता है, उदाहरण के लिए, थ्रेड द्वारा , एक ही समय में Aएक और थ्रेड (थ्रेड ) इसे प्राप्त नहीं कर सकता है। Bम्यूटेक्स जारी होने तक इसे इंतजार करना होगा।

नियम 2।

विधि पहले होती है Thread.start()। फिर, यहाँ कुछ भी मुश्किल नहीं है। आप पहले से ही जानते हैं कि विधि के अंदर कोड चलाना शुरू करने के लिए , आपको थ्रेड पर विधि को कॉल करना होगा। विशेष रूप से, प्रारंभ विधि, स्वयं विधि नहीं! यह नियम सुनिश्चित करता है कि कॉल करने से पहले सेट किए गए सभी वेरिएबल्स के मान एक बार शुरू होने के बाद विधि के अंदर दिखाई देंगे । Thread.run()run()start()run()Thread.start()run()

नियम 3।

run()विधि का अंत विधि से वापसी से पहले होता है । join()आइए अपने दो धागों पर लौटते हैं: Aऔर B। हम join()विधि को कॉल करते हैं ताकि थ्रेड को अपना काम करने से पहले Bथ्रेड के पूरा होने की प्रतीक्षा करने की गारंटी हो । Aइसका मतलब यह है कि ए ऑब्जेक्ट की run()विधि बहुत अंत तक चलने की गारंटी है। run()और थ्रेड की विधि में होने वाले डेटा में सभी परिवर्तन Aएक सौ प्रतिशत गारंटीकृत होते हैं कि थ्रेड में Bएक बार दिखाई देने के बाद थ्रेड Aअपना काम पूरा कर लेता है ताकि वह अपना काम शुरू कर सके।

नियम 4।

volatileएक चर को लिखना उसी चर से पढ़ने से पहले होता है । जब हम volatileकीवर्ड का उपयोग करते हैं, तो हम वास्तव में हमेशा वर्तमान मान प्राप्त करते हैं। यहां तक ​​कि एक longया के साथ double(हमने पहले उन समस्याओं के बारे में बात की थी जो यहां हो सकती हैं)। जैसा कि आप पहले से ही समझते हैं, कुछ थ्रेड्स में किए गए परिवर्तन हमेशा अन्य थ्रेड्स को दिखाई नहीं देते हैं। लेकिन, निश्चित रूप से, बहुत बार ऐसी स्थितियां होती हैं जहां ऐसा व्यवहार हमें शोभा नहीं देता। मान लीजिए कि हम थ्रेड पर एक चर के लिए एक मान निर्दिष्ट करते हैं A:

int z;

….

z = 555;
यदि हमारे Bथ्रेड को कंसोल पर वेरिएबल का मान प्रदर्शित करना चाहिए z, तो यह आसानी से 0 प्रदर्शित कर सकता है, क्योंकि यह असाइन किए गए मान के बारे में नहीं जानता है। लेकिन नियम 4 गारंटी देता है कि यदि हम zवेरिएबल को के रूप में घोषित करते हैं volatile, तो एक थ्रेड पर उसके मान में परिवर्तन हमेशा दूसरे थ्रेड पर दिखाई देगा। volatileयदि हम पिछले कोड में शब्द जोड़ते हैं ...

volatile int z;

….

z = 555;
...फिर हम उस स्थिति को रोकते हैं जहां थ्रेड B0 प्रदर्शित कर सकता है। volatileवेरिएबल्स को लिखना उनसे पढ़ने से पहले होता है।
टिप्पणियां
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION