CodeGym/Java Blog/यादृच्छिक/थ्रेड्सचे व्यवस्थापन. अस्थिर कीवर्ड आणि yield() पद्धत
John Squirrels
पातळी 41
San Francisco

थ्रेड्सचे व्यवस्थापन. अस्थिर कीवर्ड आणि yield() पद्धत

यादृच्छिक या ग्रुपमध्ये प्रकाशित केले
सदस्य
हाय! आम्ही आमचा मल्टीथ्रेडिंगचा अभ्यास सुरू ठेवतो. volatileआज आपण कीवर्ड आणि पद्धत जाणून घेणार आहोत yield(). चला आत जाऊया :)

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

मल्टीथ्रेडेड ऍप्लिकेशन्स तयार करताना, आम्ही दोन गंभीर समस्यांना सामोरे जाऊ शकतो. प्रथम, जेव्हा मल्टीथ्रेडेड ऍप्लिकेशन चालू असते, तेव्हा भिन्न थ्रेड्स व्हेरिएबल्सची मूल्ये कॅशे करू शकतात (आम्ही याविषयी आधीच 'अस्थिर वापरणे' शीर्षकाच्या धड्यात बोललो आहोत ). तुमच्याकडे अशी परिस्थिती असू शकते जिथे एक थ्रेड व्हेरिएबलचे मूल्य बदलतो, परंतु दुसर्‍या थ्रेडमध्ये बदल दिसत नाही, कारण ते व्हेरिएबलच्या कॅशेड कॉपीसह कार्य करत आहे. स्वाभाविकच, त्याचे परिणाम गंभीर असू शकतात. समजा की हे फक्त जुने व्हेरिएबल नाही तर तुमच्या बँक खात्यातील शिल्लक आहे, जे अचानक यादृच्छिकपणे वर आणि खाली उडी मारायला लागते :) ते मजेदार वाटत नाही, बरोबर? दुसरे, Java मध्ये, सर्व आदिम प्रकार वाचण्यासाठी आणि लिहिण्यासाठी ऑपरेशन्स,longdouble, अणू आहेत. ठीक आहे, उदाहरणार्थ, जर तुम्ही intएका थ्रेडवर व्हेरिएबलचे मूल्य बदलले आणि दुसर्‍या थ्रेडवर तुम्ही व्हेरिएबलचे मूल्य वाचले, तर तुम्हाला एकतर त्याचे जुने मूल्य मिळेल किंवा नवीन, म्हणजे बदलामुळे मिळालेले मूल्य. थ्रेड 1 मध्ये. कोणतीही 'मध्यवर्ती मूल्ये' नाहीत. longतथापि, हे s आणि s सह कार्य करत नाही double. का? क्रॉस-प्लॅटफॉर्म समर्थनामुळे. सुरुवातीच्या स्तरांवर लक्षात ठेवा की आम्ही सांगितले की Java चे मार्गदर्शक तत्व 'एकदा लिहा, कुठेही चालवा' आहे? म्हणजे क्रॉस-प्लॅटफॉर्म सपोर्ट. दुसऱ्या शब्दांत, Java अॅप्लिकेशन सर्व प्रकारच्या वेगवेगळ्या प्लॅटफॉर्मवर चालते. उदाहरणार्थ, Windows ऑपरेटिंग सिस्टमवर, Linux किंवा MacOS च्या भिन्न आवृत्त्या. या सर्वांवर कोणतीही अडचण न येता चालेल. 64 बिट्समध्ये वजन,longdoubleJava मधील 'सर्वात भारी' आदिम आहेत. आणि काही 32-बिट प्लॅटफॉर्म केवळ 64-बिट व्हेरिएबल्सचे अणू वाचन आणि लेखन लागू करत नाहीत. अशी व्हेरिएबल्स दोन ऑपरेशन्समध्ये वाचली आणि लिहिली जातात. प्रथम, पहिले 32 बिट व्हेरिएबलमध्ये लिहिलेले आहेत, आणि नंतर आणखी 32 बिट्स लिहिले आहेत. परिणामी, एक समस्या उद्भवू शकते. एक थ्रेड व्हेरिएबलवर काही 64-बिट मूल्य लिहितो Xआणि दोन ऑपरेशन्समध्ये असे करतो. त्याच वेळी, दुसरा थ्रेड व्हेरिएबलचे मूल्य वाचण्याचा प्रयत्न करतो आणि त्या दोन ऑपरेशन्समध्ये असे करतो - जेव्हा पहिले 32 बिट लिहिले गेले होते, परंतु दुसऱ्या 32 बिट्समध्ये नाही. परिणामी, ते मध्यवर्ती, चुकीचे मूल्य वाचते आणि आमच्याकडे एक बग आहे. उदाहरणार्थ, जर अशा प्लॅटफॉर्मवर आम्ही 9223372036854775809 वर नंबर लिहिण्याचा प्रयत्न करतो. व्हेरिएबलमध्ये, ते 64 बिट व्यापेल. बायनरी स्वरूपात, हे असे दिसते: 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 पहिला थ्रेड व्हेरिएबलमध्ये संख्या लिहिण्यास प्रारंभ करतो. प्रथम, ते पहिले 32 बिट्स (100000000000000000000000000000000000000000) आणि नंतर दुसरे 32 बिट्स (00000000000000000000000000001) लिहितात. आणि व्हेरिएबलचे इंटरमीडिएट व्हॅल्यू (10000000000000000000000000000000000) वाचून दुसरा थ्रेड या ऑपरेशन्समध्ये जोडला जाऊ शकतो, जे आधीपासून लिहिलेले पहिले 32 बिट आहेत. दशांश प्रणालीमध्ये, ही संख्या 2,147,483,648 आहे. दुसऱ्या शब्दांत, आम्हाला व्हेरिएबलमध्ये फक्त 9223372036854775809 हा क्रमांक लिहायचा होता, परंतु हे ऑपरेशन काही प्लॅटफॉर्मवर अणू नसल्यामुळे, आमच्याकडे दुष्ट क्रमांक 2,147,483,648 आहे, जो कोठूनही बाहेर आला आहे आणि त्याचा अज्ञात परिणाम होईल. कार्यक्रम दुसरा थ्रेड लिहिण्याआधी व्हेरिएबलचे मूल्य वाचतो, म्हणजे थ्रेडने पहिले 32 बिट पाहिले, परंतु दुसरे 32 बिट पाहिले नाहीत. अर्थात, या समस्या काल निर्माण झाल्या नाहीत. Java त्यांना एकाच कीवर्डसह सोडवते: volatile. आम्ही वापरल्यासvolatileआमच्या प्रोग्राममध्ये काही व्हेरिएबल घोषित करताना कीवर्ड…
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
... याचा अर्थ असा की:
  1. ते नेहमी अणुरीत्या वाचले आणि लिहिले जाईल. जरी ते 64-बिट doubleकिंवा long.
  2. Java मशीन ते कॅशे करणार नाही. त्यामुळे तुमच्याकडे अशी परिस्थिती नसेल जिथे 10 थ्रेड त्यांच्या स्वतःच्या स्थानिक प्रतींसह काम करत असतील.
अशा प्रकारे, दोन अतिशय गंभीर समस्या फक्त एका शब्दाने सोडवल्या जातात :)

उत्पन्न() पद्धत

आम्ही Threadवर्गाच्या अनेक पद्धतींचे आधीच पुनरावलोकन केले आहे, परंतु एक महत्त्वाची आहे जी तुमच्यासाठी नवीन असेल. ती yield()पद्धत आहे . आणि त्याच्या नावाचा अर्थ नेमका तेच करतो! थ्रेड्सचे व्यवस्थापन.  अस्थिर कीवर्ड आणि 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-2. Thread-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 ने कार्यान्वित करणे पूर्ण केले आहे. थ्रेड-० ने कार्यान्वित करणे पूर्ण केले आहे. थ्रेड-2 ने कार्यान्वित करणे पूर्ण केले आहे. अर्थात, थ्रेड शेड्युलर वेगळ्या क्रमाने थ्रेड सुरू करू शकतो (उदाहरणार्थ, 0-1-2 ऐवजी 2-1-0), परंतु तत्त्व समान राहते.

घडते-नियमांपूर्वी

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

नियम १.

म्युटेक्स रिलीझ करणे समान मॉनिटर दुसर्‍या थ्रेडद्वारे प्राप्त करण्यापूर्वी होते . मला वाटते की तुम्हाला येथे सर्वकाही समजले आहे. एखाद्या वस्तूचे किंवा वर्गाचे म्युटेक्स एका थ्रेडने मिळवले असल्यास., उदाहरणार्थ, थ्रेडद्वारे 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, तेव्हा आपल्याला नेहमीच वर्तमान मूल्य मिळते. अगदी a longकिंवा सह double(आम्ही येथे होऊ शकणार्‍या समस्यांबद्दल आधी बोललो होतो). तुम्ही आधीच समजून घेतल्याप्रमाणे, काही थ्रेडवर केलेले बदल इतर थ्रेड्सना नेहमी दिसत नाहीत. परंतु, अर्थातच, अशा बर्‍याच वेळा परिस्थिती असतात जिथे असे वर्तन आपल्यास अनुकूल नसते. समजा आपण थ्रेडवरील व्हेरिएबलला व्हॅल्यू नियुक्त करतो A:
int z;.

z = 555;
जर आमच्या थ्रेडने कन्सोलवर व्हेरिएबलचे Bमूल्य प्रदर्शित केले असेल , तर ते सहजपणे 0 प्रदर्शित करू शकते, कारण त्यास नियुक्त केलेल्या मूल्याबद्दल माहिती नसते. zपरंतु नियम 4 हमी देतो की जर आपण व्हेरिएबल zम्हणून घोषित केले volatile, तर एका थ्रेडवरील त्याच्या मूल्यातील बदल दुसर्‍या थ्रेडवर नेहमी दृश्यमान असतील. volatileजर आपण मागील कोडमध्ये शब्द जोडला तर ...
volatile int z;.

z = 555;
...तर आम्ही थ्रेड 0 दर्शवू शकतो अशा परिस्थितीला प्रतिबंध करतो. व्हेरिएबल्सवर Bलिहिणे volatileत्यांच्याकडून वाचण्यापूर्वी होते.
टिप्पण्या
  • लोकप्रिय
  • नवीन
  • जुने
टिप्पणी करण्यासाठी तुम्ही साईन इन केलेले असणे आवश्यक आहे
या पानावर अजून कोणत्याही टिप्पण्या नाहीत