परिचय
तो, हम जानते हैं कि जावा में थ्रेड्स हैं। आप इसके बारे में बेहतर एक साथ समीक्षा में पढ़ सकते हैं : जावा और थ्रेड क्लास। भाग I - निष्पादन के सूत्र । समानांतर में कार्य करने के लिए थ्रेड्स आवश्यक हैं। इससे यह अत्यधिक संभावना है कि धागे किसी तरह एक दूसरे के साथ बातचीत करेंगे। आइए देखें कि यह कैसे होता है और हमारे पास कौन से बुनियादी उपकरण हैं।उपज
थ्रेड.यील्ड () चौंकाने वाला है और शायद ही कभी इस्तेमाल किया जाता है। इंटरनेट पर इसका कई तरह से वर्णन किया गया है। जिसमें कुछ लोग लिख रहे हैं कि थ्रेड्स की कुछ कतार है, जिसमें थ्रेड प्राथमिकताओं के आधार पर एक थ्रेड उतरेगा। अन्य लोग लिखते हैं कि एक थ्रेड अपनी स्थिति को "रनिंग" से "रननेबल" में बदल देगा (भले ही इन स्थितियों के बीच कोई अंतर न हो, यानी जावा उनके बीच अंतर नहीं करता है)। वास्तविकता यह है कि यह सब बहुत कम प्रसिद्ध है और फिर भी एक अर्थ में सरल है। विधि के प्रलेखन के लिए एक बग ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) लॉग किया गया है ।yield()
यदि आप इसे पढ़ते हैं, तो यह स्पष्ट हो जाता है किyield()
विधि वास्तव में केवल जावा थ्रेड शेड्यूलर को कुछ अनुशंसा प्रदान करती है कि इस थ्रेड को कम निष्पादन समय दिया जा सकता है। लेकिन वास्तव में क्या होता है, यानी क्या अनुसूचक सिफारिश पर काम करता है और यह सामान्य रूप से क्या करता है, यह जेवीएम के कार्यान्वयन और ऑपरेटिंग सिस्टम पर निर्भर करता है। और यह कुछ अन्य कारकों पर भी निर्भर हो सकता है। सभी भ्रम इस तथ्य के कारण सबसे अधिक संभावना है कि मल्टीथ्रेडिंग पर पुनर्विचार किया गया है क्योंकि जावा भाषा विकसित हुई है। यहां अवलोकन में और पढ़ें: जावा थ्रेड.यील्ड () का संक्षिप्त परिचय ।
नींद
इसके निष्पादन के दौरान एक धागा सो सकता है। यह अन्य धागों के साथ सबसे आसान प्रकार की बातचीत है। हमारे जावा कोड को चलाने वाली जावा वर्चुअल मशीन को चलाने वाले ऑपरेटिंग सिस्टम का अपना थ्रेड शेड्यूलर होता है । यह तय करता है कि कौन सा थ्रेड शुरू करना है और कब। एक प्रोग्रामर इस अनुसूचक के साथ सीधे जावा कोड से, केवल JVM के माध्यम से बातचीत नहीं कर सकता है। वह अनुसूचक से थ्रेड को कुछ समय के लिए रोकने के लिए कह सकता है, अर्थात उसे सुलाने के लिए कह सकता है। आप इन लेखों में अधिक पढ़ सकते हैं: थ्रेड.स्लीप () और मल्टीथ्रेडिंग कैसे काम करता है । आप यह भी देख सकते हैं कि विंडोज ऑपरेटिंग सिस्टम में थ्रेड कैसे काम करते हैं: विंडोज थ्रेड के आंतरिक । और अब आइए इसे अपनी आंखों से देखें। निम्न कोड को नाम की फ़ाइल में सहेजेंHelloWorldApp.java
:
class HelloWorldApp {
public static void main(String []args) {
Runnable task = () -> {
try {
int secToWait = 1000 * 60;
Thread.currentThread().sleep(secToWait);
System.out.println("Woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(task);
thread.start();
}
}
जैसा कि आप देख सकते हैं, हमारे पास कुछ कार्य हैं जो 60 सेकंड तक प्रतीक्षा करते हैं, जिसके बाद कार्यक्रम समाप्त हो जाता है। हम "" कमांड का उपयोग करके संकलित करते हैं और फिर " " javac HelloWorldApp.java
का उपयोग करके प्रोग्राम चलाते हैं । java HelloWorldApp
प्रोग्राम को एक अलग विंडो में शुरू करना सबसे अच्छा है। उदाहरण के लिए, विंडोज़ पर, यह इस तरह है: start java HelloWorldApp
. हम पीआईडी (प्रोसेस आईडी) प्राप्त करने के लिए jps कमांड का उपयोग करते हैं, और हम थ्रेड्स की सूची "के साथ खोलते हैं jvisualvm --openpid pid
: जैसा कि आप देख सकते हैं, हमारे थ्रेड में अब" स्लीपिंग "स्थिति है। वास्तव में, मदद करने का एक और शानदार तरीका है हमारे धागे में मीठे सपने हैं:
try {
TimeUnit.SECONDS.sleep(60);
System.out.println("Woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
क्या आपने देखा कि हम InterruptedException
हर जगह संभाल रहे हैं? आइए समझते हैं क्यों।
थ्रेड.इंटरप्ट ()
बात यह है कि जब कोई धागा प्रतीक्षा कर रहा है/सो रहा है, तो कोई बाधा डालना चाहता है। इस मामले में, हम एकInterruptedException
. Thread.stop()
विधि को पदावनत घोषित किए जाने के बाद, यानी पुराना और अवांछनीय घोषित किए जाने के बाद यह तंत्र बनाया गया था । कारण यह था कि जब stop()
विधि को बुलाया गया था, तो धागा बस "मारा गया" था, जो बहुत ही अप्रत्याशित था। हम नहीं जान सकते थे कि थ्रेड कब रुकेगा, और हम डेटा स्थिरता की गारंटी नहीं दे सकते। कल्पना करें कि थ्रेड मारे जाने पर आप फ़ाइल में डेटा लिख रहे हैं। थ्रेड को मारने के बजाय, जावा के रचनाकारों ने फैसला किया कि इसे यह बताना अधिक तर्कसंगत होगा कि इसे बाधित किया जाना चाहिए। इस जानकारी का जवाब कैसे देना है यह थ्रेड के लिए ही तय करने का मामला है। अधिक विवरण के लिए, थ्रेड.स्टॉप को बहिष्कृत क्यों किया गया है?ओरेकल की वेबसाइट पर। आइए एक उदाहरण देखें:
public static void main(String []args) {
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
};
Thread thread = new Thread(task);
thread.start();
thread.interrupt();
}
इस उदाहरण में, हम 60 सेकंड प्रतीक्षा नहीं करेंगे। इसके बजाय, हम तुरंत "बाधित" प्रदर्शित करेंगे। ऐसा इसलिए है क्योंकि हमने interrupt()
थ्रेड पर मेथड को कॉल किया है। यह विधि "इंटरप्ट स्टेटस" नामक एक आंतरिक ध्वज सेट करती है। यही है, प्रत्येक धागे में एक आंतरिक ध्वज होता है जो सीधे पहुंच योग्य नहीं होता है। लेकिन हमारे पास इस झंडे के साथ बातचीत करने के मूल तरीके हैं। लेकिन यही एकमात्र तरीका नहीं है। एक धागा चल रहा हो सकता है, किसी चीज की प्रतीक्षा नहीं कर रहा है, बस क्रियाएं कर रहा है। लेकिन यह अनुमान लगा सकता है कि अन्य लोग एक विशिष्ट समय पर अपना कार्य समाप्त करना चाहेंगे। उदाहरण के लिए:
public static void main(String []args) {
Runnable task = () -> {
while(!Thread.currentThread().isInterrupted()) {
// Do some work
}
System.out.println("Finished");
};
Thread thread = new Thread(task);
thread.start();
thread.interrupt();
}
उपरोक्त उदाहरण में, while
लूप को तब तक निष्पादित किया जाएगा जब तक कि थ्रेड बाहरी रूप से बाधित न हो जाए। ध्वज के लिए isInterrupted
, यह जानना महत्वपूर्ण है कि यदि हम एक पकड़ते हैं InterruptedException
, तो बाधित ध्वज रीसेट हो जाता है, और फिर isInterrupted()
झूठी वापसी करेगा। थ्रेड क्लास में एक स्थिर थ्रेड.इंटरप्टेड () विधि भी होती है जो केवल वर्तमान थ्रेड पर लागू होती है, लेकिन यह विधि फ़्लैग को गलत पर रीसेट करती है! थ्रेड रुकावट नामक इस अध्याय में और पढ़ें ।
शामिल हों (दूसरे थ्रेड के समाप्त होने की प्रतीक्षा करें)
प्रतीक्षा का सबसे सरल प्रकार किसी अन्य थ्रेड के समाप्त होने की प्रतीक्षा कर रहा है।
public static void main(String []args) throws InterruptedException {
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
};
Thread thread = new Thread(task);
thread.start();
thread.join();
System.out.println("Finished");
}
इस उदाहरण में, नया थ्रेड 5 सेकंड सोएगा। उसी समय, मुख्य धागा तब तक इंतजार करेगा जब तक कि सोता हुआ धागा जाग न जाए और अपना काम पूरा न कर ले। यदि आप JVisualVM में थ्रेड की स्थिति को देखते हैं, तो यह इस तरह दिखेगा: मॉनिटरिंग टूल के लिए धन्यवाद, आप देख सकते हैं कि थ्रेड के साथ क्या हो रहा है। विधि join
बहुत सरल है, क्योंकि यह केवल जावा कोड के साथ एक विधि है जो तब तक निष्पादित होती है wait()
जब तक कि जिस धागे पर इसे कहा जाता है वह जीवित है। जैसे ही धागा मर जाता है (जब यह अपना काम पूरा कर लेता है), प्रतीक्षा बाधित हो जाती है। join()
और वह सब विधि का जादू है । तो चलिए सबसे दिलचस्प बात पर चलते हैं।
निगरानी करना
मल्टीथ्रेडिंग में मॉनिटर की अवधारणा शामिल है। शब्द मॉनिटर 16वीं शताब्दी के लैटिन के माध्यम से अंग्रेजी में आया है और इसका अर्थ है "एक उपकरण या उपकरण जिसका उपयोग किसी प्रक्रिया के निरंतर रिकॉर्ड को देखने, जांचने या रखने के लिए किया जाता है"। इस लेख के संदर्भ में, हम मूलभूत बातों को शामिल करने का प्रयास करेंगे। विवरण चाहने वाले किसी भी व्यक्ति के लिए, कृपया लिंक की गई सामग्री में गोता लगाएँ। हम अपनी यात्रा Java Language Specification (JLS) के साथ शुरू करते हैं: 17.1। तुल्यकालन । यह निम्नलिखित कहता है: यह पता चला है कि जावा धागे के बीच सिंक्रनाइज़ेशन के लिए "मॉनिटर" तंत्र का उपयोग करता है। प्रत्येक वस्तु के साथ एक मॉनिटर जुड़ा होता है, और थ्रेड्स इसे प्राप्त कर सकते हैंlock()
या इसके साथ जारी कर सकते हैं unlock()
। अगला, हम Oracle वेबसाइट पर ट्यूटोरियल पाएंगे: Intrinsic Locks and Synchronization. यह ट्यूटोरियल कहता है कि जावा का सिंक्रोनाइज़ेशन एक आंतरिक इकाई के आसपास बनाया गया है जिसे इंट्रिन्सिक लॉक या मॉनिटर लॉक कहा जाता है । इस लॉक को अक्सर " मॉनिटर " कहा जाता है। हम फिर से यह भी देखते हैं कि जावा में प्रत्येक वस्तु के साथ एक आंतरिक ताला जुड़ा हुआ है। आप Java - Intrinsic Locks and Synchronization पढ़ सकते हैं । आगे यह समझना महत्वपूर्ण होगा कि जावा में किसी वस्तु को मॉनिटर से कैसे जोड़ा जा सकता है। जावा में, प्रत्येक ऑब्जेक्ट में एक हेडर होता है जो आंतरिक मेटाडेटा को स्टोर करता है जो कोड से प्रोग्रामर के लिए उपलब्ध नहीं होता है, लेकिन वर्चुअल मशीन को ऑब्जेक्ट के साथ सही ढंग से काम करने की आवश्यकता होती है। ऑब्जेक्ट हेडर में एक "मार्क वर्ड" शामिल होता है, जो इस तरह दिखता है:
https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf
public class HelloWorld{
public static void main(String []args){
Object object = new Object();
synchronized(object) {
System.out.println("Hello World");
}
}
}
यहां, वर्तमान धागा (जिस पर कोड की इन पंक्तियों को निष्पादित किया गया है) synchronized
कीवर्ड का उपयोग मॉनिटर से जुड़े मॉनिटर का उपयोग करने का प्रयास करने के लिए करता हैobject"\
लॉक प्राप्त/प्राप्त करने के लिए चर। यदि कोई और मॉनिटर के लिए प्रतिस्पर्धा नहीं कर रहा है (अर्थात कोई भी उसी वस्तु का उपयोग करके सिंक्रनाइज़ कोड नहीं चला रहा है), तो जावा "पक्षपातपूर्ण लॉकिंग" नामक अनुकूलन करने का प्रयास कर सकता है। एक प्रासंगिक टैग और एक रिकॉर्ड जिसके बारे में थ्रेड मॉनिटर के लॉक का मालिक है, ऑब्जेक्ट हेडर में मार्क शब्द में जोड़ा जाता है। यह मॉनिटर को लॉक करने के लिए आवश्यक ओवरहेड को कम करता है। यदि मॉनिटर पहले किसी अन्य थ्रेड के स्वामित्व में था, तो ऐसा लॉकिंग पर्याप्त नहीं है। जेवीएम अगले प्रकार के लॉकिंग पर स्विच करता है: "बेसिक लॉकिंग"। यह तुलना-और-स्वैप (CAS) संचालन का उपयोग करता है। क्या अधिक है, ऑब्जेक्ट हेडर का मार्क शब्द अब मार्क शब्द को संग्रहीत नहीं करता है, बल्कि यह कहाँ संग्रहीत किया जाता है, इसका संदर्भ देता है, और टैग बदल जाता है ताकि JVM समझ सके कि हम बुनियादी लॉकिंग का उपयोग कर रहे हैं। यदि मॉनिटर के लिए कई थ्रेड्स प्रतिस्पर्धा करते हैं (प्रतियोगिता करते हैं) (एक ने लॉक हासिल कर लिया है, और दूसरा लॉक के रिलीज़ होने की प्रतीक्षा कर रहा है), तो मार्क शब्द में टैग बदल जाता है, और मार्क शब्द अब मॉनिटर के संदर्भ को संग्रहीत करता है एक वस्तु के रूप में - जेवीएम की कुछ आंतरिक इकाई। जैसा कि JDK एन्हांसमेंट प्रपोजल (JEP) में कहा गया है, इस स्थिति में इस इकाई को स्टोर करने के लिए मेमोरी के नेटिव हीप क्षेत्र में जगह की आवश्यकता होती है। इस आंतरिक इकाई की मेमोरी लोकेशन का संदर्भ ऑब्जेक्ट हेडर के मार्क वर्ड में स्टोर किया जाएगा। इस प्रकार, एक मॉनिटर वास्तव में कई थ्रेड्स के बीच साझा संसाधनों तक पहुंच को सिंक्रनाइज़ करने के लिए एक तंत्र है। जेवीएम इस तंत्र के कई कार्यान्वयनों के बीच स्विच करता है। तो, सरलता के लिए, जब मॉनिटर के बारे में बात करते हैं, हम वास्तव में तालों के बारे में बात कर रहे हैं। और एक सेकंड लॉक के रिलीज़ होने की प्रतीक्षा कर रहा है), फिर मार्क शब्द में टैग बदल जाता है, और मार्क शब्द अब मॉनिटर के संदर्भ को ऑब्जेक्ट के रूप में संग्रहीत करता है - JVM की कुछ आंतरिक इकाई। जैसा कि JDK एन्हांसमेंट प्रपोजल (JEP) में कहा गया है, इस स्थिति में इस इकाई को स्टोर करने के लिए मेमोरी के नेटिव हीप क्षेत्र में जगह की आवश्यकता होती है। इस आंतरिक इकाई की मेमोरी लोकेशन का संदर्भ ऑब्जेक्ट हेडर के मार्क वर्ड में स्टोर किया जाएगा। इस प्रकार, एक मॉनिटर वास्तव में कई थ्रेड्स के बीच साझा संसाधनों तक पहुंच को सिंक्रनाइज़ करने के लिए एक तंत्र है। जेवीएम इस तंत्र के कई कार्यान्वयनों के बीच स्विच करता है। तो, सरलता के लिए, जब मॉनिटर के बारे में बात करते हैं, हम वास्तव में तालों के बारे में बात कर रहे हैं। और एक सेकंड लॉक के रिलीज़ होने की प्रतीक्षा कर रहा है), फिर मार्क शब्द में टैग बदल जाता है, और मार्क शब्द अब मॉनिटर के संदर्भ को ऑब्जेक्ट के रूप में संग्रहीत करता है - JVM की कुछ आंतरिक इकाई। जैसा कि JDK एन्हांसमेंट प्रपोजल (JEP) में कहा गया है, इस स्थिति में इस इकाई को स्टोर करने के लिए मेमोरी के नेटिव हीप क्षेत्र में जगह की आवश्यकता होती है। इस आंतरिक इकाई की मेमोरी लोकेशन का संदर्भ ऑब्जेक्ट हेडर के मार्क वर्ड में स्टोर किया जाएगा। इस प्रकार, एक मॉनिटर वास्तव में कई थ्रेड्स के बीच साझा संसाधनों तक पहुंच को सिंक्रनाइज़ करने के लिए एक तंत्र है। जेवीएम इस तंत्र के कई कार्यान्वयनों के बीच स्विच करता है। तो, सरलता के लिए, जब मॉनिटर के बारे में बात करते हैं, हम वास्तव में तालों के बारे में बात कर रहे हैं। और चिह्न शब्द अब एक वस्तु के रूप में मॉनिटर के संदर्भ को संग्रहीत करता है - जेवीएम की कुछ आंतरिक इकाई। जैसा कि JDK एन्हांसमेंट प्रपोजल (JEP) में कहा गया है, इस स्थिति में इस इकाई को स्टोर करने के लिए मेमोरी के नेटिव हीप क्षेत्र में जगह की आवश्यकता होती है। इस आंतरिक इकाई की मेमोरी लोकेशन का संदर्भ ऑब्जेक्ट हेडर के मार्क वर्ड में स्टोर किया जाएगा। इस प्रकार, एक मॉनिटर वास्तव में कई थ्रेड्स के बीच साझा संसाधनों तक पहुंच को सिंक्रनाइज़ करने के लिए एक तंत्र है। जेवीएम इस तंत्र के कई कार्यान्वयनों के बीच स्विच करता है। तो, सरलता के लिए, जब मॉनिटर के बारे में बात करते हैं, हम वास्तव में तालों के बारे में बात कर रहे हैं। और चिह्न शब्द अब एक वस्तु के रूप में मॉनिटर के संदर्भ को संग्रहीत करता है - जेवीएम की कुछ आंतरिक इकाई। जैसा कि JDK एन्हांसमेंट प्रपोजल (JEP) में कहा गया है, इस स्थिति में इस इकाई को स्टोर करने के लिए मेमोरी के नेटिव हीप क्षेत्र में जगह की आवश्यकता होती है। इस आंतरिक इकाई की मेमोरी लोकेशन का संदर्भ ऑब्जेक्ट हेडर के मार्क वर्ड में स्टोर किया जाएगा। इस प्रकार, एक मॉनिटर वास्तव में कई थ्रेड्स के बीच साझा संसाधनों तक पहुंच को सिंक्रनाइज़ करने के लिए एक तंत्र है। जेवीएम इस तंत्र के कई कार्यान्वयनों के बीच स्विच करता है। तो, सरलता के लिए, जब मॉनिटर के बारे में बात करते हैं, हम वास्तव में तालों के बारे में बात कर रहे हैं। इस आंतरिक इकाई की मेमोरी लोकेशन का संदर्भ ऑब्जेक्ट हेडर के मार्क वर्ड में स्टोर किया जाएगा। इस प्रकार, एक मॉनिटर वास्तव में कई थ्रेड्स के बीच साझा संसाधनों तक पहुंच को सिंक्रनाइज़ करने के लिए एक तंत्र है। जेवीएम इस तंत्र के कई कार्यान्वयनों के बीच स्विच करता है। तो, सरलता के लिए, जब मॉनिटर के बारे में बात करते हैं, हम वास्तव में तालों के बारे में बात कर रहे हैं। इस आंतरिक इकाई की मेमोरी लोकेशन का संदर्भ ऑब्जेक्ट हेडर के मार्क वर्ड में स्टोर किया जाएगा। इस प्रकार, एक मॉनिटर वास्तव में कई थ्रेड्स के बीच साझा संसाधनों तक पहुंच को सिंक्रनाइज़ करने के लिए एक तंत्र है। जेवीएम इस तंत्र के कई कार्यान्वयनों के बीच स्विच करता है। तो, सरलता के लिए, जब मॉनिटर के बारे में बात करते हैं, हम वास्तव में तालों के बारे में बात कर रहे हैं।
सिंक्रनाइज़ (लॉक के लिए प्रतीक्षारत)
जैसा कि हमने पहले देखा, "सिंक्रोनाइज़्ड ब्लॉक" (या "क्रिटिकल सेक्शन") की अवधारणा मॉनिटर की अवधारणा से निकटता से संबंधित है। एक उदाहरण देखें:
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Runnable task = () -> {
synchronized(lock) {
System.out.println("thread");
}
};
Thread th1 = new Thread(task);
th1.start();
synchronized(lock) {
for (int i = 0; i < 8; i++) {
Thread.currentThread().sleep(1000);
System.out.print(" " + i);
}
System.out.println(" ...");
}
}
यहां, मुख्य धागा पहले टास्क ऑब्जेक्ट को नए थ्रेड में पास करता है, और फिर तुरंत लॉक प्राप्त करता है और इसके साथ एक लंबा ऑपरेशन (8 सेकंड) करता है। इस समय, कार्य आगे बढ़ने में असमर्थ है, क्योंकि यह synchronized
ब्लॉक में प्रवेश नहीं कर सकता, क्योंकि लॉक पहले से ही अधिग्रहित है। यदि थ्रेड को लॉक नहीं मिल सकता है, तो वह मॉनिटर की प्रतीक्षा करेगा। जैसे ही इसे लॉक मिलेगा, यह निष्पादन जारी रखेगा। जब कोई थ्रेड मॉनिटर से बाहर निकलता है, तो यह लॉक को रिलीज़ करता है। JVisualVM में, यह ऐसा दिखता है: जैसा कि आप JVisualVM में देख सकते हैं, स्थिति "मॉनीटर" है, जिसका अर्थ है कि थ्रेड अवरुद्ध है और मॉनिटर नहीं ले सकता। आप थ्रेड की स्थिति निर्धारित करने के लिए कोड का उपयोग भी कर सकते हैं, लेकिन इस तरह निर्धारित स्थिति के नाम JVisualVM में उपयोग किए गए नामों से मेल नहीं खाते, हालांकि वे समान हैं। इस मामले में,th1.getState()
लूप के लिए स्टेटमेंट BLOCKED वापस आ जाएगा , क्योंकि जब तक लूप चल रहा है, lock
ऑब्जेक्ट का मॉनिटर थ्रेड द्वारा कब्जा कर लिया गया है main
, और th1
थ्रेड ब्लॉक हो गया है और लॉक जारी होने तक आगे नहीं बढ़ सकता है। सिंक्रोनाइज़्ड ब्लॉक्स के अलावा, एक पूरी विधि को सिंक्रोनाइज़ किया जा सकता है। उदाहरण के लिए, यहाँ HashTable
वर्ग से एक विधि है:
public synchronized int size() {
return count;
}
यह विधि किसी भी समय केवल एक थ्रेड द्वारा निष्पादित की जाएगी। क्या हमें वास्तव में ताला चाहिए? हाँ, हमें इसकी आवश्यकता है। इंस्टेंस विधियों के मामले में, "यह" ऑब्जेक्ट (वर्तमान ऑब्जेक्ट) लॉक के रूप में कार्य करता है। इस विषय पर यहां एक दिलचस्प चर्चा है: क्या सिंक्रोनाइज़्ड ब्लॉक के बजाय सिंक्रोनाइज़्ड मेथड का उपयोग करने का कोई फायदा है? . यदि विधि स्थिर है, तो लॉक "यह" ऑब्जेक्ट नहीं होगा (क्योंकि स्थिर विधि के लिए कोई "यह" ऑब्जेक्ट नहीं है), बल्कि क्लास ऑब्जेक्ट (उदाहरण के लिए,) Integer.class
।
प्रतीक्षा करें (मॉनिटर की प्रतीक्षा कर रहा है)। सूचित करें () और सूचित करें सभी () विधियाँ
थ्रेड क्लास में एक और वेटिंग मेथड है जो मॉनिटर से जुड़ी है।sleep()
और के विपरीत join()
, इस विधि को केवल कॉल नहीं किया जा सकता है। इसका नाम है wait()
। विधि wait
को उस मॉनिटर से जुड़े ऑब्जेक्ट पर कॉल किया जाता है जिसका हम इंतजार करना चाहते हैं। आइए एक उदाहरण देखें:
public static void main(String []args) throws InterruptedException {
Object lock = new Object();
// The task object will wait until it is notified via lock
Runnable task = () -> {
synchronized(lock) {
try {
lock.wait();
} catch(InterruptedException e) {
System.out.println("interrupted");
}
}
// After we are notified, we will wait until we can acquire the lock
System.out.println("thread");
};
Thread taskThread = new Thread(task);
taskThread.start();
// We sleep. Then we acquire the lock, notify, and release the lock
Thread.currentThread().sleep(3000);
System.out.println("main");
synchronized(lock) {
lock.notify();
}
}
JVisualVM में, यह ऐसा दिखता है: यह समझने के लिए कि यह कैसे काम करता है, याद रखें कि wait()
और notify()
विधियाँ java.lang.Object
. यह अजीब लग सकता है कि थ्रेड-संबंधित विधियाँ Object
कक्षा में हैं। लेकिन इसकी वजह अब सामने आई है। आपको याद होगा कि Java में हर object का एक Header होता है। हेडर में विभिन्न हाउसकीपिंग जानकारी होती है, जिसमें मॉनिटर के बारे में जानकारी, यानी लॉक की स्थिति शामिल होती है। याद रखें, प्रत्येक वस्तु, या एक वर्ग का उदाहरण, JVM में एक आंतरिक इकाई से जुड़ा होता है, जिसे इंट्रिन्सिक लॉक या मॉनिटर कहा जाता है। lock
उपरोक्त उदाहरण में, टास्क ऑब्जेक्ट के लिए कोड इंगित करता है कि हम ऑब्जेक्ट से जुड़े मॉनिटर के लिए सिंक्रोनाइज़्ड ब्लॉक दर्ज करते हैं। यदि हम इस मॉनीटर के लिए ताला प्राप्त करने में सफल हो जाते हैं, तबwait()
कहा जाता है। कार्य निष्पादित करने वाला थ्रेड ऑब्जेक्ट के मॉनीटर को रिलीज़ करेगा , लेकिन ऑब्जेक्ट के मॉनीटर lock
से अधिसूचना की प्रतीक्षा कर रहे धागे की कतार में प्रवेश करेगा। lock
थ्रेड्स की इस कतार को WAIT SET कहा जाता है, जो इसके उद्देश्य को अधिक सही ढंग से दर्शाती है। यानी, यह एक कतार से अधिक एक सेट है। थ्रेड main
टास्क ऑब्जेक्ट के साथ एक नया थ्रेड बनाता है, इसे शुरू करता है और 3 सेकंड तक प्रतीक्षा करता है। इससे यह अत्यधिक संभावना है कि नया थ्रेड थ्रेड से पहले लॉक प्राप्त करने में सक्षम होगा main
, और मॉनीटर की कतार में आ जाएगा। उसके बाद, main
थ्रेड स्वयं lock
ऑब्जेक्ट के सिंक्रोनाइज़्ड ब्लॉक में प्रवेश करता है और मॉनिटर का उपयोग करके थ्रेड नोटिफिकेशन करता है। सूचना भेजे जाने के बाद, main
थ्रेड जारी करता हैlock
ऑब्जेक्ट का मॉनिटर, और नया थ्रेड, जो पहले lock
ऑब्जेक्ट के मॉनिटर के रिलीज़ होने की प्रतीक्षा कर रहा था, निष्पादन जारी रखता है। notify()
कतार में केवल एक थ्रेड ( ) या एक साथ सभी थ्रेड्स को एक सूचना भेजना संभव है ( notifyAll()
)। यहां और पढ़ें: Java में Inform() और InformAll() के बीच अंतर । यह नोट करना महत्वपूर्ण है कि अधिसूचना क्रम इस बात पर निर्भर करता है कि JVM कैसे कार्यान्वित किया जाता है। यहां और पढ़ें: नोटिफ़िकेशन और नोटिफ़िकेशन के साथ भुखमरी का समाधान कैसे करें? . किसी वस्तु को निर्दिष्ट किए बिना तुल्यकालन किया जा सकता है। आप ऐसा तब कर सकते हैं जब कोड के एक ब्लॉक के बजाय पूरी विधि सिंक्रनाइज़ हो। उदाहरण के लिए, स्थिर विधियों के लिए, लॉक एक क्लास ऑब्जेक्ट होगा (द्वारा प्राप्त .class
):
public static synchronized void printA() {
System.out.println("A");
}
public static void printB() {
synchronized(HelloWorld.class) {
System.out.println("B");
}
}
तालों के उपयोग के संदर्भ में, दोनों विधियाँ समान हैं। यदि कोई विधि स्थिर नहीं है, तो वर्तमान का उपयोग करके instance
, अर्थात, का उपयोग करके सिंक्रनाइज़ेशन किया जाएगा this
। वैसे, हमने पहले कहा था कि आप getState()
थ्रेड की स्थिति जानने के लिए विधि का उपयोग कर सकते हैं। उदाहरण के लिए, कतार में एक मॉनिटर के लिए प्रतीक्षा कर रहे थ्रेड के लिए, यदि wait()
विधि टाइमआउट निर्दिष्ट करती है, तो स्थिति प्रतीक्षा या TIMED_WAITING होगी।
https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java
थ्रेड जीवन चक्र
अपने जीवन के दौरान, थ्रेड की स्थिति बदल जाती है। वास्तव में, इन परिवर्तनों में थ्रेड का जीवन चक्र शामिल होता है। जैसे ही एक थ्रेड बनाया जाता है, उसकी स्थिति नई होती है। इस अवस्था में, नया थ्रेड अभी तक नहीं चल रहा है और जावा थ्रेड शेड्यूलर को अभी इसके बारे में कुछ भी पता नहीं है। थ्रेड शेड्यूलर को थ्रेड के बारे में जानने के लिए, आपकोthread.start()
विधि को कॉल करना होगा। फिर थ्रेड रननेबल स्थिति में परिवर्तित हो जाएगा। इंटरनेट में बहुत सारे गलत चित्र हैं जो "रननेबल" और "रनिंग" राज्यों के बीच अंतर करते हैं। लेकिन यह एक गलती है, क्योंकि जावा "रेडी टू वर्क" (रननेबल) और "वर्किंग" (रनिंग) के बीच अंतर नहीं करता है। जब कोई धागा जीवित है लेकिन सक्रिय नहीं है (चलाने योग्य नहीं), यह दो राज्यों में से एक में है:
- अवरुद्ध - एक महत्वपूर्ण खंड, यानी एक
synchronized
ब्लॉक में प्रवेश करने की प्रतीक्षा कर रहा है। - WAITING - किसी शर्त को पूरा करने के लिए किसी अन्य थ्रेड की प्रतीक्षा करना।
getState()
विधि का उपयोग करें। थ्रेड्स में एक विधि भी होती है isAlive()
, जो थ्रेड समाप्त नहीं होने पर सही होती है।
लॉक सपोर्ट और थ्रेड पार्किंग
जावा 1.6 से शुरू होकर, LockSupport नामक एक दिलचस्प तंत्र दिखाई दिया। यह वर्ग इसका उपयोग करने वाले प्रत्येक थ्रेड के साथ "परमिट" को जोड़ता है।park()
यदि परमिट उपलब्ध है, तो प्रक्रिया में परमिट का उपभोग करते हुए, विधि के लिए एक कॉल तुरंत वापस आ जाती है। नहीं तो ब्लॉक कर देता है। विधि को कॉल करने से unpark
परमिट उपलब्ध हो जाता है यदि यह अभी तक उपलब्ध नहीं है। केवल 1 परमिट है। के लिए जावा प्रलेखन वर्ग LockSupport
को संदर्भित करता है Semaphore
। आइए एक साधारण उदाहरण देखें:
import java.util.concurrent.Semaphore;
public class HelloWorldApp{
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(0);
try {
semaphore.acquire();
} catch (InterruptedException e) {
// Request the permit and wait until we get it
e.printStackTrace();
}
System.out.println("Hello, World!");
}
}
यह कोड हमेशा प्रतीक्षा करेगा, क्योंकि अब सेमाफोर में 0 परमिट हैं। और जब acquire()
कोड में कहा जाता है (यानी परमिट का अनुरोध करें), थ्रेड तब तक प्रतीक्षा करता है जब तक कि उसे परमिट प्राप्त न हो जाए। चूंकि हम प्रतीक्षा कर रहे हैं, हमें संभालना चाहिए InterruptedException
। दिलचस्प बात यह है कि सेमाफोर को एक अलग थ्रेड स्टेट मिलता है। यदि हम JVisualVM में देखते हैं, तो हम देखेंगे कि राज्य "प्रतीक्षा" नहीं है, बल्कि "पार्क" है। आइए एक और उदाहरण देखें:
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
// Park the current thread
System.err.println("Will be Parked");
LockSupport.park();
// As soon as we are unparked, we will start to act
System.err.println("Unparked");
};
Thread th = new Thread(task);
th.start();
Thread.currentThread().sleep(2000);
System.err.println("Thread state: " + th.getState());
LockSupport.unpark(th);
Thread.currentThread().sleep(2000);
}
थ्रेड की स्थिति प्रतीक्षारत होगी, लेकिन JVisualVM कीवर्ड और कक्षा से wait
अलग करता है । यह इतना महत्वपूर्ण क्यों है? हम फिर से जावा प्रलेखन की ओर मुड़ते हैं और WAITING थ्रेड स्थिति को देखते हैं। जैसा कि आप देख सकते हैं, इसमें प्रवेश करने के केवल तीन तरीके हैं। उनमें से दो तरीके हैं और । और तीसरा है । जावा में, तालों को टी पर भी बनाया जा सकता है और उच्च-स्तरीय उपकरण प्रदान करता है। आइए एक का उपयोग करने का प्रयास करें। उदाहरण के लिए, इस पर एक नज़र डालें : synchronized
park
LockSupport
LockSupport
wait()
join()
LockSupport
LockSuppor
ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{
public static void main(String []args) throws InterruptedException {
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
System.out.println("Thread");
lock.unlock();
};
lock.lock();
Thread th = new Thread(task);
th.start();
System.out.println("main");
Thread.currentThread().sleep(2000);
lock.unlock();
}
}
पिछले उदाहरणों की तरह, यहाँ सब कुछ सरल है। ऑब्जेक्ट lock
साझा संसाधन को रिलीज़ करने के लिए किसी की प्रतीक्षा करता है। यदि हम JVisualVM में देखते हैं, तो हम देखेंगे कि नया थ्रेड तब तक पार्क किया जाएगा जब तक कि main
थ्रेड लॉक को रिलीज़ नहीं कर देता। आप यहां ताले के बारे में अधिक पढ़ सकते हैं: Java 8 StampedLocks बनाम ReadWriteLocks and Synchronized and Lock API in Java। ताले कैसे लागू किए जाते हैं, इसे बेहतर ढंग से समझने के लिए, इस लेख में फेजर के बारे में पढ़ना उपयोगी है: जावा फेजर के लिए गाइड । और विभिन्न सिंक्रोनाइज़र के बारे में बोलते हुए, आपको जावा सिंक्रोनाइज़र पर DZone लेख अवश्य पढ़ना चाहिए।
निष्कर्ष
इस समीक्षा में, हमने जावा में थ्रेड्स के इंटरैक्ट करने के मुख्य तरीकों की जांच की। अतिरिक्त सामग्री:- बेहतर एक साथ: जावा और थ्रेड क्लास। भाग I - निष्पादन के सूत्र
- https://dzone.com/articles/the-java-synchronizers
- https://www.javatpoint.com/java-multithreading-interview-questions
GO TO FULL VERSION