व्यावहारिक मल्टीथ्रेडिंग बेस्ट प्रैक्टिस: Java संस्करण — वर्चुअल थ्रेड युग की रीतियाँ

· · मल्टीथ्रेडिंग, Java, व्यावसायिक अनुप्रयोग, बग जाँच, डिज़ाइन

«हम Java में एक व्यावसायिक बैच कार्य समांतर करना चाहते हैं।» «हमारे Spring वेब ऐप में साझा कैश कभी-कभी खराब हो जाता है।» «हमें new Thread से भरा पुराना Swing ऐप विरासत में मिला।» — Java में JDK 1.0 से भाषा में मल्टीथ्रेडिंग बनी हुई है, java.util.concurrent के औज़ार-बक्से तक परिपक्व हुई है, और JDK 21 के वर्चुअल थ्रेड से समवर्ती प्रोग्रामिंग की पारंपरिक समझ फिर लिखी है। औज़ार इतने समृद्ध हैं, इसलिए आप जो औज़ार चुनते हैं वही डिज़ाइन की गुणवत्ता बन जाता है।

यह लेख मल्टीथ्रेडिंग-व्यवहार श्रृंखला का Java संस्करण है। उन डेवलपरों के लिए जो Java में व्यावसायिक सिस्टम, बैच कार्य और सर्वर अनुप्रयोग लिखते हैं, यह मल्टीथ्रेडिंग डिज़ाइन के सिद्धांतों — थ्रेड सीधे कभी न बनाएँ, साझा परिवर्तनीय अवस्था घटाएँ, लॉकिंग में अनुशासन, सबसे पहले रोकने का डिज़ाइन — को Java के औज़ारों पर मैप करता है (मुख्यतः LTS रिलीज़, JDK 21 और बाद), और अगस्त 2026 तक के प्राथमिक स्रोतों पर वर्चुअल-थ्रेड युग के चुनाव तथा Java-विशिष्ट जाल समेटता है। इसे अकेले पढ़ा जा सके, ऐसा लिखा गया है। अन्य भाषाओं पर लागू वही सिद्धांत साथी लेखों «.NET संस्करण», «C++ संस्करण» और «C संस्करण» में हैं।

1. निष्कर्ष पहले

  • व्यावसायिक कोड में new Thread न लिखें — यही नियम Java में भी लागू होता है। कार्य ExecutorService को सौंपें और थ्रेड जीवनचक्र प्रबंधन लाइब्रेरी पर छोड़ें।1
  • जो कार्य मुख्यतः I/O की प्रतीक्षा करते हैं उन्हें वर्चुअल थ्रेड पर भेजें। JDK 21 में औपचारिक वर्चुअल थ्रेड प्रति कार्य एक इस्तेमाल होते हैं और कभी पूल नहीं होने चाहिए। समवर्तीता Semaphore से सीमित करें, पूल आकार से नहीं।23
  • वर्चुअल थ्रेड थ्रूपुट का औज़ार हैं, गणना तेज़ करने का नहीं। CPU-बाउंड समांतरण अभी भी लगभग कोर संख्या के प्लेटफ़ॉर्म थ्रेड का काम है — स्थिर पूल या parallel stream, पहले जैसा।3
  • private final लॉक ऑब्जेक्ट पर लॉक करें, या समर्पित ReentrantLock पर। synchronized(this) और सार्वजनिक रूप से उजागर ऑब्जेक्ट पर लॉक बाहरी कोड से टकरा सकता है। समयबद्ध प्राप्ति (tryLock) चाहिए तो ReentrantLock
  • JDK 21-23 में synchronized ब्लॉक के अंदर अवरोधन वर्चुअल थ्रेड पिन करता था; JDK 24 (JEP 491) ने ठीक किया। पुरानी चेतावनियों और वर्तमान वास्तविकता में भेद करें।34
  • volatile दृश्यता और क्रम की गारंटी देता है, अणुता की नहीं। काउंटर के लिए AtomicInteger / LongAdder, संयुक्त अवस्था के लिए लॉक।5
  • Interruption से सहयोगी रोक थ्रेड रोकने का एकमात्र सही तरीका है। Thread.stop / suspend / resume अब UnsupportedOperationException फेंकते हैं। InterruptedException न निगलें — स्थिति पुनर्स्थापित करें या ऊपर फेंकें।6
  • ExecutorService को दो-चरण पैटर्न से रोकें: shutdown → awaitTermination → shutdownNow। shutdownNow best-effort है (मानक कार्यान्वयन interruption से), इसलिए यह मानता है कि आपके कार्य interruption का जवाब देते हैं।1
  • Swing का UI विशेष रूप से EDT (Event Dispatch Thread) का है। अन्य थ्रेड से अद्यतन SwingUtilities.invokeLater से माँगें।7

2. मल्टीथ्रेडिंग कठिन क्यों है — रेस कंडीशन, डेडलॉक, और मेमोरी मॉडल

मल्टीथ्रेडिंग जो समस्याएँ लाती है, भाषा चाहे जो हो, उबालें तो दो तरह की हैं।

रेस कंडीशन वह बग है जिसमें परिणाम इस बात पर निर्भर करता है कि कई थ्रेड किसी कोड खंड तक किस क्रम में पहुँचते हैं। क्लासिक उदाहरण साझा काउंटर है: एक अभिव्यक्ति count++ वास्तव में तीन चरणों में टूटती है — पढ़ो, जोड़ो, वापस लिखो। यदि दो थ्रेड इन तीन चरणों में एक साथ प्रवेश करें, एक थ्रेड की वृद्धि दूसरे के लिखने पर अधिलेखित होकर खो जाती है। हर चलान पर परिणाम बदलता है, और कौन-सा परिणाम मिलेगा, अनुमान नहीं लग सकता।

थ्रेड Bसाझा चर countथ्रेड Aथ्रेड Bसाझा चर countथ्रेड Acount = 10दो वृद्धि हुईं,पर count = 11 — थ्रेड A की वृद्धि खो गईपढ़ना (10)पढ़ना (10)स्थानीय जोड़ (11)स्थानीय जोड़ (11)वापस लिखना (11)वापस लिखना (11)

चित्र 1: क्लासिक रेस कंडीशन जिसमें साझा काउंटर पर वृद्धि खो जाती है। यदि count++ के तीन चरणों के दौरान दूसरा थ्रेड बीच में आ जाए, तो जो लेखन सबसे बाद होता है वह दूसरे को अधिलेखित कर देता है

डेडलॉक वह अवस्था है जिसमें दो थ्रेड प्रत्येक उस लॉक की प्रतीक्षा करते हैं जो दूसरा पकड़े हुए है, इसलिए कोई आगे नहीं बढ़ सकता। थ्रेड A लॉक 1 पकड़े लॉक 2 की प्रतीक्षा करता है; थ्रेड B लॉक 2 पकड़े लॉक 1 की प्रतीक्षा करता है — इतना ही दोनों को हमेशा के लिए रोकने के लिए काफी है।

लॉक 2 के मुक्त होने की प्रतीक्षालॉक 1 के मुक्त होने की प्रतीक्षाथ्रेड Aलॉक 1 पकड़ेथ्रेड Bलॉक 2 पकड़े

चित्र 2: डेडलॉक की वृत्ताकार प्रतीक्षा। जिस क्षण प्रतीक्षा तीर लूप बनाते हैं, उस लूप के अंदर हर थ्रेड हमेशा के लिए रुक जाता है

दोनों समय-निर्भर हैं। वह अंतर्संयोजन जो विकास मशीन पर दसियों हज़ार चलानों में एक बार दिखे, अलग कोर संख्या और भार वाले उत्पादन सर्वर पर रोज़ हो सकता है। «डिबगर लगाने पर पुनरुत्पादन बंद» और «लॉग जोड़ने पर गायब» दोनों इसलिए कि अवलोकन समय बदल देता है — रेस बग का क्लासिक व्यवहार। ठीक इसलिए इस लेख का हर सिद्धांत सही सिंक्रनाइज़ करने की चिंता से पहले, सिंक्रनाइज़ेशन वाले स्थान घटाने की दिशा में है।

2.1. Java-विशिष्ट आधार — मेमोरी मॉडल और happens-before

उसके ऊपर, Java-विशिष्ट यह है कि साझा डेटा कैसे दिखता है Java Memory Model (JMM) के happens-before संबंधों से परिभाषित होता है।

बिना सिंक्रनाइज़ेशन साझा चर पढ़ना-लिखना C++-शैली «अपरिभाषित व्यवहार» नहीं बनता, पर वैध रूप से पुराने मान दिखते रहना, या लेखन क्रम से बाहर दिखना हो सकता है। मेमोरी संगति त्रुटि जिसमें «लूप boolean ध्वज देख रहा है, पर दूसरे थ्रेड का बदला मान कभी नहीं दिखता» JMM द्वारा अनुमत व्यवहार है, JVM बग नहीं।5 इससे बचाने वाले औज़ार वे तंत्र हैं जो happens-before बनाते हैं — synchronized, volatile, और java.util.concurrent की कक्षाएँ। समवर्ती संग्रह सही इस्तेमाल करें तो लाइब्रेरी «अद्यतन संक्रिया और बाद की प्राप्ति के बीच happens-before» की गारंटी देती है।8

अर्थात् Java का व्यावहारिक मार्गदर्शन यह है: कच्चे साझा चर से चतुराई न करें। साझा करने के लिए java.util.concurrent के औज़ार इस्तेमाल करें, और happens-before लाइब्रेरी बनवाएँ।

3. थ्रेड कैसे बनाएँ — ExecutorService और वर्चुअल थ्रेड

3.1. कार्य को चलाने के तरीके से अलग करना

ExecutorService Java में «थ्रेड स्वयं न बनाएँ» सिद्धांत को ढोता है। यह काम (Runnable / Callable) को इस से अलग करता है कि वह कैसे चले — कितने थ्रेड, कौन-सी कतार — और थ्रेड निर्माण, पुनः उपयोग, निपटान लाइब्रेरी पर छोड़ता है।1

JDK 21 से, कैसे चलाना चुनना सरल द्विआधारी चुनाव बन गया है।23

मुख्यतः I/O प्रतीक्षाHTTP कॉल, DB, फ़ाइलेंCPU-बाउंड गणनाकार्य जिसे समवर्ती चलाना हैकार्य को क्या चलाता है?वर्चुअल थ्रेडExecutors.newVirtualThreadPerTaskExecutorप्रति कार्य एक, कभी पूल नहींप्लेटफ़ॉर्म थ्रेड का स्थिर पूलExecutors.newFixedThreadPool - लगभग कोर जितनेया parallel streamबाहरी सेवाओं की समवर्तीताSemaphore से सीमित करें, पूल आकार से नहीं

चित्र 3: JDK 21 से कार्य चलाने का चुनाव। पहले रेखा खींचें — «I/O की प्रतीक्षा बदलें, CPU के लिए समांतर करें» — फिर I/O-बाउंड काम वर्चुअल थ्रेड को और CPU-बाउंड काम पारंपरिक पूल को सौंपें

CPU-बाउंड पक्ष पर एक सावधानी है। Executors.newFixedThreadPool थ्रेड संख्या सीमित करता है, पर उसकी कतार असीमित है। लंबे समय चलने वाली सेवा में जहाँ प्रस्तुतियाँ प्रसंस्करण से आगे निकलती रहें, केवल थ्रेड कोर संख्या तक सीमित हैं — कतार में जमा कार्य और उनके डेटा स्मृति खाते रहते हैं। उस तरह के सेटअप में, या तो ThreadPoolExecutor सीधे इस्तेमाल कर सीमित कतार प्लस अस्वीकृति नीति कॉन्फ़िगर करें, या प्रस्तुति पक्ष पर Semaphore जैसी प्रवेश नियंत्रण रखें ताकि पश्च-दाब लगा सकें (खंड 4 की कतार चर्चा जैसा ही सिद्धांत)।

3.2. वर्चुअल थ्रेड का दुरुपयोग न करें

वर्चुअल थ्रेड OS थ्रेड से अलग हल्के थ्रेड हैं: JDK में अवरोधन संक्रिया (मानक लाइब्रेरी में I/O, लॉकिंग, स्लीप आदि) के दौरान वे अपना OS थ्रेड छोड़ देते हैं, इसलिए एक JVM लाखों चला सकती है। फिर भी वे हर तरह के अवरोधन पर नहीं छोड़ते। यदि वर्चुअल थ्रेड नेटिव कोड (JNI) या विदेशी फ़ंक्शन चलाते हुए अवरुद्ध हो, तो वह अपने वाहक थ्रेड से पिन रहता है। जो JDK 24 (JEP 491, नीचे) ने ठीक किया वह synchronized से पिनिंग थी; नेटिव सीमा पर पिनिंग रहती है, इसलिए JNI ड्राइवर या डिवाइस API से लंबे अवरोधन वाले ऑपरेशन भारी संख्या में वर्चुअल थ्रेड पर लादना वाहक थ्रेड खत्म कर देगा। पर आधिकारिक मार्गदर्शक ज़ोर देता है, वे «तेज़ थ्रेड» नहीं हैं। कोड निष्पादन गति नहीं बदलती — जो वे देते हैं वह पैमाना (थ्रूपुट) है।3

इस्तेमाल के तीन अनुशासन हैं।3

  1. उन्हें पूल न करें। वर्चुअल थ्रेड सस्ते और डिस्पोजेबल हैं; «कार्यों की संख्या = वर्चुअल थ्रेड की संख्या» सही अवस्था है। newFixedThreadPool में वर्चुअल थ्रेड डालना ग़लती है — रूप try (var executor = Executors.newVirtualThreadPerTaskExecutor()) इस्तेमाल करें।
  2. समवर्तीता Semaphore से सीमित करें। «बाहरी API से अधिकतम 10 समवर्ती कनेक्शन» जैसा अवरोध सेमाफोर से व्यक्त करें, पूल आकार से नहीं।
  3. CPU-बाउंड काम के लिए इस्तेमाल न करें। लगभग कोर संख्या के प्लेटफ़ॉर्म थ्रेड गणना समांतर करने के लिए पहले की तरह सही औज़ार हैं।

ध्यान दें कि वर्चुअल थ्रेड के अंदर साधारण तुल्यकालिक कोड चलता है। .NET के async/await की तरह कोड फिर लिखने के बजाय, वर्चुअल थ्रेड के पीछे डिज़ाइन दर्शन यह है कि आप सीधे «प्रति अनुरोध एक थ्रेड» कोड, अपरिवर्तित, विशाल पैमाने पर चला सकें।2

3.3. आम ग़लतफ़हमी — «पूलिंग की ज़रूरत नहीं» केवल वर्चुअल थ्रेड पर लागू होता है

«उन्हें पूल न करें» अनुशासन को यह न पढ़ें कि «Java में थ्रेड पूल जैसी चीज़ नहीं (या वह अक्षम है)»। वास्तविकता उलटी है: Java के पूल JDK 5 (2004) से मानक लाइब्रेरी का परिपक्व हिस्सा हैं। बारीक कॉन्फ़िगर होने वाला सामान्य पूल ThreadPoolExecutor (Executors की विभिन्न फ़ैक्टरियों से बना), कार्य-चोरी वाला ForkJoinPool (जिसका साझा इंस्टेंस commonPool() parallel stream और CompletableFuture का डिफ़ॉल्ट निष्पादन लक्ष्य है), और आवधिक निष्पादन के लिए ScheduledThreadPoolExecutor — CPU-बाउंड काम में ये अभी भी प्रमुख हैं।

पूल मूलतः इस पर आधारित अनुकूलन है कि «OS थ्रेड बनाना और पकड़े रखना महँगा है, इसलिए पुनः इस्तेमाल करें»। वर्चुअल थ्रेड निर्माण लागत लगभग शून्य कर इस आधार को मिटाते हैं, इसलिए उन्हें पुनः इस्तेमाल करने का कारण नहीं रहा — सटीक समझ यह नहीं कि पूलिंग अक्षम हो गई, बल्कि थ्रेड इतने हल्के हो गए कि पूलिंग अनुकूलन अनावश्यक है। और वर्चुअल थ्रेड के नीचे, JDK का अनुसूचक लगभग कोर संख्या के वाहक थ्रेड (OS थ्रेड) कार्य-चोरी ForkJoinPool के रूप में चलाता है।2 अर्थात् «OS थ्रेड के छोटे पूल से विशाल समवर्ती काम सँभालना» रूप बचा रहता है; केवल उस पूल का प्रबंधन डेवलपर के हाथ से JVM को गया। कहना उचित है कि Java उसी मंज़िल पर पहुँचती है जहाँ .NET का async/await await पर थ्रेड पूल को लौटाता है, कोड का रूप बदले बिना।

4. साझा परिवर्तनीय अवस्था घटाना — विभाजन, अपरिवर्तनीयता, समवर्ती संग्रह, और कतारें

प्रतिस्पर्धा तभी उठती है जब «कई थ्रेड» और «साझा परिवर्तनीय डेटा» दोनों हों। थ्रेड संख्या आवश्यकताएँ तय करती हैं, इसलिए डिज़ाइन जो काट सकता है वह साझा हिस्सा है। तीन परिवार की तकनीकें हैं — विभाजन, अपरिवर्तनीय बनाना, और डेटा सौंपना — और Java में उन्हें ऐसे लिखते हैं।

विभाजित करें। समांतर संकलन में, हर थ्रेड के साझा योग चर पर लिखने के बजाय, हर थ्रेड आंशिक परिणाम बनाए और अंत में मिलाएँ। Parallel stream के reduce / collect ठीक यही रूप ढाँचे के रूप में देते हैं, और नीचे चर्चित LongAdder भी विभाजन रणनीति का कार्यान्वयन है — आंतरिक रूप से कक्षों में बाँटकर प्रतिस्पर्धा फैलाता है, और पढ़ते समय जोड़ता है। साझा अवस्था पर कितनी बार लिखें, यह घटाना सही सिंक्रनाइज़ लिखने से पहले आता है।

अपरिवर्तनीय बनाएँ। record और अपरिवर्तनीय संग्रहों (List.copyOf / Map.copyOf) से ऐसा डेटा बनाएँ जो निर्माण के बाद फिर न लिखा जाए, और बिना सिंक्रनाइज़ेशन साझा कर सकते हैं। कॉन्फ़िगरेशन और मास्टर डेटा के लिए मानक पैटर्न: जब बदलना हो, नया ऑब्जेक्ट बनाएँ और volatile संदर्भ बदलें। पर «केवल-पठन दिखता है» और «अपरिवर्तनीय है» अलग चीज़ें हैं। record के ऐक्सेसर अपने घटकों के कच्चे संदर्भ लौटाते हैं, और List.copyOf की प्रति भी उथली है (तत्व ऑब्जेक्ट नहीं दोहराती), इसलिए यदि तत्व परिवर्तनीय हैं, उपनाम पकड़े कोई भी सामग्री फिर लिख सकता है, और प्रतिस्पर्धा रहती है। बिना सिंक्रनाइज़ेशन साझा करना तभी सुरक्षित है जब पूरा ऑब्जेक्ट ग्राफ़ — तत्वों सहित — अपरिवर्तनीय हो। यदि परिवर्तनीय तत्व शामिल हैं, या तो गहरी प्रति दें या तत्वों को भी record / अपरिवर्तनीय प्रकारों की ओर धकेलें।

समवर्ती संग्रहों की संयुक्त संक्रियाएँ इस्तेमाल करें। ConcurrentHashMap के «यदि अनुपस्थित हो तो बनाएँ, फिर डालें» पैटर्न के लिए computeIfAbsent। यह विधि पूरी कॉल अणु रूप से निष्पादित करती है, और यदि कुंजी अनुपस्थित हो तो मैपिंग फ़ंक्शन उस एक कॉल के भीतर ठीक एक बार बुलाया जाता है8 गारंटी .NET के ConcurrentDictionary.GetOrAdd से भिन्न है (जिसकी फ़ैक्टरी प्रतिस्पर्धा में एक से अधिक बार चल सकती है) — वह बिंदु जो दोनों भाषाओं के बीच आने-जाने वाले आसानी से मिलाते हैं। पर यह «कुंजी के जीवन में ठीक एक बार» नहीं है। यदि फ़ंक्शन null लौटाए या फेंके, कोई मैपिंग पंजीकृत नहीं होती, और बाद की कॉल पर फ़ंक्शन फिर चलता है (पंजीकरण के बाद प्रविष्टि हटाने पर भी यही)। यदि आपकी आरंभीकरण दोहराए गए पार्श्व प्रभाव सहन नहीं कर सकती, डिज़ाइन करें कि फ़ंक्शन सफल हो और गैर-null लौटाए। अणु होने की कीमत पर, गणना चलते कुछ अन्य थ्रेड के अद्यतन अवरुद्ध होते हैं, इसलिए मैपिंग फ़ंक्शन छोटा रखें, और फ़ंक्शन के भीतर इसी मैप को कभी अद्यतन न करें (पहचाना गया पुनरावर्ती अद्यतन IllegalStateException फेंक सकता है)।8

// आवृत्ति काउंटर का मानक पैटर्न: computeIfAbsent + LongAdder
ConcurrentHashMap<String, LongAdder> freqs = new ConcurrentHashMap<>();
freqs.computeIfAbsent(key, k -> new LongAdder()).increment();

कतार से डेटा सौंपें। थ्रेडों के बीच डेटा प्रवाह BlockingQueue से भेजें। क्षमता दिए ArrayBlockingQueue पर, भरा होने पर put अवरुद्ध होता है, स्वाभाविक पश्च-दाब देता है — .NET संस्करण के bounded चैनल जैसा ही रूप। वर्चुअल-थ्रेड युग में भी, उत्पादक और उपभोक्ता के बीच स्पष्ट सीमा खींचने का यह डिज़ाइन प्रभावी रहता है।

5. लॉकिंग अनुशासन — synchronized और ReentrantLock

5.1. किस पर लॉक करें, और लॉक पकड़े क्या न करें

लॉकिंग की इकाई «कोड खंड» नहीं बल्कि «डेटा» सोचें। हर परिवर्तनीय डेटा समुच्चय की रक्षा के लिए एक लॉक ऑब्जेक्ट मैप करें, और उस डेटा को छूने वाले हर स्थान पर वही लॉक लें — रेस बग की वास्तविकता आमतौर पर यह है कि यह मैपिंग कहीं टूट गई। synchronized(this) और synchronized(SomeClass.class) से बचें, क्योंकि बाहरी कोड उसी ऑब्जेक्ट पर लॉक कर सकता है; इसके बजाय, रक्षा किए जाने वाले डेटा को एक-से-एक private final Object lock = new Object(); जो बाहर कभी न उजागर हो से जोड़ें।

उस पर दो और अनुशासन। पहला, लॉक पकड़े कुछ धीमा न करें, न कुछ जो बाहरी दुनिया छुए। लॉक अभी पकड़े I/O, श्रोता बुलाना, या अज्ञात कोड चलाना पकड़ की अवधि बढ़ाता है और जोखिम है कि बुलाया गया दूसरा लॉक लेने की कोशिश करे, चित्र 2 की वृत्ताकार प्रतीक्षा बनाए। दूसरा, कई लॉकों के लिए प्राप्ति क्रम स्थिर करें। जहाँ दो या अधिक लॉक लें, नियम बनाएँ कि हर थ्रेड उन्हें उसी क्रम में ले, और जहाँ क्रम गारंटी नहीं दे सकते, नीचे चर्चित tryLock(timeout) से «न मिले तो छोड़कर फिर कोशिश» पथ तैयार करें।

synchronized «छोटे, सरल बहिष्करण» के लिए पर्याप्त है। निम्न चाहिए तो ReentrantLock पर जाएँ।

  • tryLock(timeout) से समयबद्ध प्राप्ति (स्थायी हैंग को ऐसे विफलता में बदलना जिसे लॉग और सँभाल सकें)
  • निष्पक्षता नीतियाँ, कई Condition, या जब लॉक प्राप्ति और मुक्ति अलग विधियों में बाँटना चाहें

ReentrantLock इस्तेमाल करते lock() के तुरंत बाद try और finally में unlock() का पैटर्न न तोड़ें (Java में C++ के RAII जैसा कुछ नहीं, इसलिए यही पैटर्न पूरा अनुशासन है)।

5.2. वर्चुअल थ्रेड और पिनिंग — JDK 24 में क्या बदला

जब वर्चुअल थ्रेड पहली बार आए (JDK 21-23), बाधा यह थी कि synchronized ब्लॉक के अंदर अवरोधन वर्चुअल थ्रेड को उसके OS थ्रेड से पिन करता था (OS थ्रेड नहीं छोड़ सकता, पैमाने का लाभ खोता), और बार-बार या लंबे अवरोधन स्थलों को ReentrantLock से बदलने की सिफ़ारिश थी।3 वह बाधा JDK 24 के JEP 491 ने मॉनिटर कार्यान्वयन फिर लिखकर हल की, और synchronized अब वर्चुअल थ्रेड पिन नहीं करता।4 यदि आप JDK 24 या बाद पर हैं, पिनिंग के प्रतिकार के रूप में synchronized यंत्रवत् बदलना अब आवश्यक नहीं। जाँचना ज़रूरी है कि आपके संगठन के पुराने दिशानिर्देश अभी भी JDK 21-युग की चेतावनी पर तो नहीं अटके।

5.3. अणु और volatile कहाँ बैठते हैं

एक चर के अणु अद्यतन AtomicInteger / AtomicLong / AtomicReference सँभालते हैं (या, केवल उच्च आवृत्ति पर बढ़ने वाले आँकड़ों के लिए, प्रतिस्पर्धा-प्रतिरोधी LongAdder)। volatile दृश्यता और क्रम (happens-before) की गारंटी देता है, संयुक्त संक्रियाओं की अणुता नहीं।5 .NET और C++ संस्करणों जैसा ही निष्कर्ष Java में भी है: ध्वज और एकल मानों के लिए अणु, संयुक्त अवस्था के लिए लॉक, और volatile अकेले से न करवाएँ।

6. रोकने का डिज़ाइन — interruption एक साझा भाषा के रूप में

6.1. interrupt की शिष्टाचार

Java में रोक और रद्दीकरण interruption के इर्द-गिर्द एकीकृत हैं। t.interrupt() लक्ष्य थ्रेड की interrupt स्थिति सेट करता है, और यदि लक्ष्य sleep / wait / join आदि में अवरुद्ध हो, तो InterruptedException फेंककर तुरंत जगाता है (तब interrupt स्थिति साफ़ होती है)।6 Thread.stop / suspend / resume, अतीत के ज़बरदस्ती तंत्र, मूलतः असुरक्षित हैं, इसलिए अब कॉल करने पर UnsupportedOperationException आता है।6

स्वयं समाप्त कर सकता हैसमाप्त नहीं कर सकता - जैसे लाइब्रेरी मेंकॉलर रोकता है - t.interrupt कॉल करता हैInterrupt स्थिति सेट होती हैगणना कर रहा थ्रेड:लूप में Thread.interrupted जाँचता हैsleep / wait / join में अवरुद्ध:InterruptedException चलता है और तुरंत जगाता हैस्थिति साफ़ होती हैसाफ़ कर स्वयं समाप्त करता हैcatch क्या करता है?Thread.currentThread.interruptस्थिति पुनर्स्थापित कर संकेत छोड़ता है

चित्र 4: Interruption से सहयोगी रोक। InterruptedException निगलने से रुकने का संकेत गायब होता है — पकड़ने के बाद चुनाव «समाप्त» या «पुनर्स्थापित» है

व्यवहार में याद रखने योग्य एक ही अनुशासन है: ऐसा कोड न लिखें जो InterruptedException पकड़े और कुछ न करे। यदि अपनी ज़िम्मेदारी में समाप्त कर सकते हैं, वहीं समाप्त करें; यदि नहीं, Thread.currentThread().interrupt() से स्थिति पुनर्स्थापित करें और संकेत कॉलर को दें (FAQ देखें)।

6.2. ExecutorService का दो-चरण शटडाउन

ExecutorService के शटडाउन API interruption मॉडल पर बैठते हैं। shutdown() नए कार्य स्वीकारना बंद करता है और पहले से प्रस्तुत कार्यों को पूर्णता तक चलने देता है; shutdownNow() चल रहे कार्यों को रोकने की कोशिश करता है। इंटरफ़ेस विनिर्देश के रूप में यह best-effort है, और स्पष्ट रूप से दस्तावेज़ीकृत है कि मानक कार्यान्वयन (जैसे ThreadPoolExecutor) आमतौर पर Thread.interrupt() से रद्द करता है — अर्थात् जो कार्य interruption का जवाब न दे वह shutdownNow से भी नहीं रुकेगा, और यदि कस्टम Executor कार्यान्वयन इस्तेमाल कर रहे हैं, उसके दस्तावेज़ में देखें वह कैसे रद्द करता है (interrupt भेजता भी है या नहीं)।1 आधिकारिक दस्तावेज़ द्वारा दिखाया मानक रोक पैटर्न निम्न दो-चरण पैटर्न है।1

/** शटडाउन पूरा होने पर सत्य। यह असत्य रहते साझा संसाधन मुक्त करने न बढ़ें। */
boolean shutdownAndAwaitTermination(ExecutorService pool) {
    pool.shutdown();                    // चरण 1: नए कार्य स्वीकारना बंद और पूर्णता की प्रतीक्षा
    try {
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            // चरण 2: रद्दीकरण माँगें। चलने से पहले गिराए गए कार्य
            // लौटते हैं, इसलिए उनके Future रद्द चिह्नित करें ताकि get() पर रुके कॉलर जगें
            pool.shutdownNow().forEach(r -> {
                if (r instanceof Future<?> f) f.cancel(false);
            });
            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
                System.err.println("Pool did not terminate");
                return false;           // शटडाउन अधूरा। सफलता से अलग रूप में बताएँ
            }
        }
        return true;
    } catch (InterruptedException ex) {
        pool.shutdownNow().forEach(r -> {
            if (r instanceof Future<?> f) f.cancel(false);
        });
        Thread.currentThread().interrupt();   // इस थ्रेड की interrupt स्थिति भी पुनर्स्थापित करें
        return false;                   // यह पथ भी शटडाउन अधूरा छोड़ सकता है
    }
}
समय सीमा में समाप्तसमय समाप्तपूर्णअभी भी अधूराshutdownनए कार्य स्वीकारना बंदawaitTerminationपूर्णता की प्रतीक्षाशटडाउन पूराshutdownNowचल रहे कार्यों को interrupt भेजता हैजवाब कार्य पर निर्भरawaitTerminationफिर प्रतीक्षाविसंगति के रूप में लॉगinterrupt अनदेखा करने वाले कार्य का संदेह

चित्र 5: ExecutorService का दो-चरण शटडाउन। चरणबद्ध डिज़ाइन «विनम्रता से प्रतीक्षा → interruption से माँग → फिर भी न रुके तो विसंगति के रूप में देखें»

shutdownNow() द्वारा लौटाए Runnable रद्द करने की एक सीमा है। जो लौटता है वह निष्पादन कतार में बैठा ऑब्जेक्ट है — सादे submit के लिए वह वही FutureTask है जो कॉलर को दिया गया, पर ExecutorCompletionService जैसे रैपर से प्रस्तुत कार्यों के लिए वह कतार के अंदर का रैपर है, कॉलर के Future से अलग ऑब्जेक्ट। उस विन्यास में ऊपर का रद्दीकरण कॉलर का Future पूरा नहीं करेगा, इसलिए प्रस्तुति समय अपनी Future सूची रखें और शटडाउन पर उन्हें रद्द करें (या गिराए गए कार्य मालिक को लौटाएँ)।

close() (AutoCloseable), JDK 19 से उपलब्ध, «shutdown कॉल कर पूर्णता तक प्रतीक्षा» को try-with-resources से लिखने योग्य रूप में पैक करता है, और वर्चुअल-थ्रेड newVirtualThreadPerTaskExecutor के साथ try (var executor = ...) आधुनिक मूल रूप है।1 पर close() ऊपर के दो-चरण पैटर्न का विकल्प नहीं। क्योंकि वह बिना समय सीमा पूर्णता की प्रतीक्षा करता है, यदि एक भी कार्य interruption का जवाब न दे या कभी न रुके, बंद करने वाला थ्रेड हमेशा के लिए अवरुद्ध होता है। यह उन दायरों के लिए औज़ार है जहाँ कार्य सीमित हैं और पूर्णता गारंटीकृत है (वहीं प्रस्तुत करें, वहीं प्रतीक्षा करें); अनुप्रयोग के शटडाउन पथ जैसे स्थानों पर, जहाँ आप चाहते हैं कि हमेशा सीमित समय में समाप्त हो, समयबद्ध दो-चरण पैटर्न इस्तेमाल करें। व्यक्तिगत कार्य रद्द करना भी interruption से होता है, Future.cancel(true) से।

7. UI थ्रेड — Swing का EDT

डेस्कटॉप अनुप्रयोग, भाषा या फ़्रेमवर्क चाहे जो हो, इस नियम का पालन करते हैं कि UI उस थ्रेड का विशेष है जो उसे प्रबंधित करता है। Swing में वह विशेष थ्रेड Event Dispatch Thread (EDT) है: Swing घटक विधियाँ, नियमतः, थ्रेड-सुरक्षित नहीं हैं, और कई थ्रेड से छूना थ्रेड हस्तक्षेप और मेमोरी संगति त्रुटियाँ आमंत्रित करता है। अन्य थ्रेड से स्क्रीन अद्यतन SwingUtilities.invokeLater से EDT को माँगें, और उलटा, क्योंकि EDT पर लंबे ऑपरेशन UI जमा देते हैं, भारी काम SwingWorker आदि से वर्कर थ्रेड पर निकालें।7 JavaFX वही रूप अपनाता है: UI अद्यतन Platform.runLater से अनुप्रयोग थ्रेड पर माँगे जाते हैं।

8. सत्यापन और डिबग — थ्रेड डंप एक हथियार के रूप में

परीक्षण से रेस बग मिलने की अपेक्षा न करें। साधारण परीक्षण उस चलान को सफलता गिनते हैं जिसमें प्रतिस्पर्धा संयोग से नहीं हुई। रक्षा तीन परतों में सोचें।

पहली रक्षा रेखा डिज़ाइन है। समीक्षा में तालिका से जाँचें: कौन-सा परिवर्तनीय डेटा साझा है, प्रत्येक मद कौन-सा लॉक सुरक्षित करता है (खंड 5.1 की मैपिंग), क्या लॉक प्राप्ति क्रम अद्वितीय है, कोई catch ब्लॉक InterruptedException तो नहीं निगल रहा, और क्या रोक पथ (shutdown/interruption) हर कार्य तक पहुँचता है।

दूसरा, थ्रेड डंप का अच्छा उपयोग करें। Java के पास «इस जमे क्षण हर थ्रेड की अवस्था» पकड़ने का मानक औज़ार है: jstack (या jcmd <pid> Thread.print) स्टैक ट्रेस छापता है, और -l विकल्प लॉक जानकारी भी जोड़ता है।9 ध्यान दें कि यह पारंपरिक डंप प्रारूप प्लेटफ़ॉर्म थ्रेड के लिए है; इसमें आपके अनुप्रयोग के वर्चुअल थ्रेड नहीं आते। वर्चुअल थ्रेड वाले विन्यास (खंड 3) में अवरुद्ध अनुरोध ट्रेस करते jcmd <pid> Thread.dump_to_file -format=json <file> इस्तेमाल करें, जो वर्चुअल थ्रेड भी डंप कर सकता है।2 हैंग जाँच की मूल प्रक्रिया कुछ सेकंड के अंतर पर दो-तीन डंप लेना और मिलान करना है कि प्रत्येक निष्क्रिय थ्रेड किस लॉक की प्रतीक्षा कर रहा है, और वह लॉक कौन पकड़े है। यदि tryLock(timeout) समय समाप्ति लॉग करें (खंड 5.2), डंप लेने का ट्रिगर स्वचालित भी कर सकते हैं।

तीसरा, भार के नीचे हिलाएँ। तनाव परीक्षण — कोर से अधिक समांतरता पर लंबे समय चलाना, प्रसंस्करण क्रम यादृच्छिक करना, कृत्रिम विलंब डालना — विकास मशीन पर रेस कंडीशन का «हिट» खींचने की संभावना बढ़ाने का व्यावहारिक तरीका है। रिलीज़ से पहले कम से कम एक परीक्षण उत्पादन-पैमाने डेटा मात्रा और थ्रेड संख्या से चलाएँ।

9. Java समवर्तीता कहाँ जा रही है — Structured Concurrency

आधा कदम आगे एक त्वरित नज़र, समापन के लिए। वर्चुअल थ्रेड की मान्यता पर बना Structured Concurrency (StructuredTaskScope) — जो कई उपकार्यों को एक कार्य इकाई मानता है और विफलता प्रसार तथा रद्दीकरण संरचित करता है — विकास में है और अगस्त 2026 तक अभी पूर्वावलोकन सुविधा है। JDK 25 के पाँचवें पूर्वावलोकन (JEP 505) में इसे StructuredTaskScope.open() आधारित API रूप में संशोधित किया गया, और वर्तमान JDK 26 में छठे पूर्वावलोकन (JEP 525) के रूप में जारी है।1011 इस बीच Scoped Values, अपरिवर्तनीय संदर्भ साझाकरण जो ThreadLocal की समस्याएँ हल करता है, JDK 25 में अंतिम हुआ।12 इस लेख के सिद्धांत — स्पष्ट कार्य सीमाएँ, अपरिवर्तनीय साझाकरण, सहयोगी रोक — इन नए API की दिशा से भी मेल खाते हैं।

10. सारांश — Java जाँचसूची

  1. व्यावसायिक कोड में कोई new Thread तो नहीं बचा (ExecutorService / वर्चुअल थ्रेड पर बना है)?
  2. I/O-बाउंड और CPU-बाउंड काम अलग निष्पादन तंत्रों पर भेजे गए हैं (चित्र 3 की शाखा)?
  3. वर्चुअल थ्रेड पूल नहीं हो रहे, और समवर्तीता Semaphore से सीमित है?
  4. साझा डेटा अपरिवर्तनीय है (record / List.copyOf), या java.util.concurrent के औज़ारों पर बना है?
  5. कोई synchronized(this) या सार्वजनिक रूप से उजागर ऑब्जेक्ट पर लॉक नहीं?
  6. ConcurrentHashMap की संयुक्त संक्रियाएँ (computeIfAbsent आदि) इस्तेमाल कर मैपिंग फ़ंक्शन छोटा रख रहे हैं?
  7. volatile से अणुता की अपेक्षा नहीं (काउंटर Atomic कक्षाएँ / LongAdder इस्तेमाल करते हैं)?
  8. एक भी catch ब्लॉक InterruptedException नहीं निगल रहा?
  9. ExecutorService रोकना समयबद्ध दो-चरण पैटर्न का पालन करता है (और close() वाले स्थान उन दायरों तक सीमित हैं जहाँ कार्य पूर्णता गारंटीकृत है)?
  10. Swing/JavaFX UI अद्यतन EDT / अनुप्रयोग थ्रेड पर समेकित हैं?

Java समवर्तीता औज़ारों से सर्वाधिक सुसज्जित भाषाओं में से एक है, और वर्चुअल थ्रेड के आगमन ने सीधे तुल्यकालिक कोड को फिर लिखे बिना स्केल करने का मार्ग खोला। ठीक इसलिए औज़ारों के बीच श्रम विभाजन सही पकड़ना — कौन थ्रूपुट के लिए, कौन बहिष्करण के लिए, और रोक का संकेत क्या है — Java में मल्टीथ्रेडिंग डिज़ाइन का सार है।

संबंधित लेख

संबंधित परामर्श क्षेत्र

KomuraSoft LLC Java व्यावसायिक सिस्टम और बैच प्रसंस्करण की मल्टीथ्रेडिंग डिज़ाइन समीक्षा, साझा-अवस्था भ्रष्टाचार और «कभी-कभी नहीं रुकता / हैंग» जैसे समवर्तीता-संबंधी दोषों की मूल-कारण जाँच (थ्रेड डंप विश्लेषण), तथा वर्चुअल थ्रेड अपनाने पर तकनीकी परामर्श सँभालता है।

संदर्भ लिंक

  1. Oracle, ExecutorService (Java SE 21 & JDK 21 API). shutdown() के पहले से प्रस्तुत कार्यों को पूर्णता तक चलने देते हुए नई प्रस्तुतियाँ रोकने पर; shutdownNow() के चल रहे कार्यों को रोकने की कोशिश और निष्पादन की प्रतीक्षा कर रहे कार्यों की सूची लौटाने पर, यद्यपि विशिष्ट कार्यान्वयन Thread.interrupt() से रद्द करता है, best-effort से आगे कोई गारंटी नहीं, इसलिए जो कार्य interruption का जवाब न दे वह समाप्त नहीं होगा; awaitTermination से पूर्णता की प्रतीक्षा करने पर; close() (AutoCloseable, Java 19 से) के shutdown कॉल कर पूर्णता की प्रतीक्षा करने, try-with-resources के साथ प्रयोग योग्य होने पर; और shutdown → awaitTermination → shutdownNow दो-चरण शटडाउन के उपयोग उदाहरण के रूप में दिखाए जाने पर।  2 3 4 5 6

  2. OpenJDK, JEP 444: Virtual Threads. वर्चुअल थ्रेड के JDK 21 में आधिकारिक सुविधा बनने पर; उनके हल्के थ्रेड होने पर जो उच्च-थ्रूपुट समवर्ती अनुप्रयोग लिखने, बनाए रखने और देखने का प्रयास नाटकीय रूप से घटाते हैं; सीधे «एक अनुरोध, एक थ्रेड» तुल्यकालिक कोड अपरिवर्तित स्केल करने के डिज़ाइन दर्शन पर; JDK के वर्चुअल थ्रेड अनुसूचक के FIFO मोड में कार्य-चोरी ForkJoinPool होने पर, डिफ़ॉल्ट समांतरता उपलब्ध प्रोसेसर संख्या के बराबर; और नया थ्रेड डंप प्रारूप जो वर्चुअल थ्रेड शामिल करता है jcmd Thread.dump_to_file (सादे पाठ और JSON रूप में) के रूप में जोड़े जाने पर, जबकि पारंपरिक थ्रेड डंप वर्चुअल थ्रेड शामिल नहीं करते।  2 3 4 5

  3. Oracle Java SE Core Libraries, Virtual Threads. वर्चुअल थ्रेड के Java रनटाइम द्वारा कार्यान्वित हल्के थ्रेड होने पर जो अवरोधन I/O के दौरान अपना OS थ्रेड छोड़ते हैं; उनके गति (विलंबता) के बजाय पैमाने (थ्रूपुट) की सुविधा होने और CPU-गहन प्रसंस्करण के लिए अनुपयुक्त होने पर; वर्चुअल थ्रेड कभी न पूल करने और प्रति कार्य एक इस्तेमाल करने पर (newVirtualThreadPerTaskExecutor); समवर्तीता सीमित करने के लिए थ्रेड पूल के बजाय Semaphore इस्तेमाल करने पर; JDK 21 तक synchronized के अंदर अवरोधन के OS थ्रेड से पिनिंग करने पर, जिसके लिए बार-बार या लंबे स्थलों को ReentrantLock से बदलने की सलाह थी; और -Djdk.tracePinnedThreads से पिनिंग पहचानने पर।  2 3 4 5 6 7

  4. OpenJDK, JEP 491: Synchronize Virtual Threads without Pinning. JDK 24 के लिए JVM के मॉनिटर कार्यान्वयन के वर्चुअल थ्रेड समर्थन हेतु फिर लिखे जाने पर, ताकि synchronized ब्लॉक या विधि के अंदर अवरोधन अब वर्चुअल थ्रेड को उसके वाहक थ्रेड से पिन न करे; और इसके अर्थ पर कि JDK 21-23 युग का «synchronized को ReentrantLock से बदलें» प्रतिकार सिद्धांततः अब आवश्यक नहीं।  2

  5. Oracle, The Java Tutorials, Memory Consistency Errors. मेमोरी संगति त्रुटियों के तब उठने पर जब कई थ्रेड का उसी डेटा का असंगत दृश्य हो; उनसे बचने की कुंजी happens-before संबंध होने पर (गारंटी कि एक कथन का मेमोरी लेखन दूसरे कथन को दिखे); और synchronized, volatile, तथा Thread.start / join आदि के happens-before बनाने पर।  2 3

  6. Oracle, Thread (Java SE 21 & JDK 21 API). Thread.stop / suspend / resume के मूलतः असुरक्षित होने पर (लॉक असंगत अवस्था में मुक्त होते हैं और टूटे ऑब्जेक्ट दिखते हैं; suspend डेडलॉक आमंत्रित कर सकता है), उन्हें हटाने के लिए हतोत्साहित बनाने, और अब कॉल पर UnsupportedOperationException फेंकने पर; interrupt() के interrupt स्थिति सेट करने और sleep / wait / join में अवरुद्ध थ्रेड को InterruptedException फेंककर जगाने पर (जो interrupt स्थिति साफ़ करता है); और interrupted() तथा isInterrupted() उस स्थिति से कैसे व्यवहार करते हैं, इस अंतर पर।  2 3

  7. Oracle, The Java Tutorials, The Event Dispatch Thread. Swing के ईवेंट-हैंडलिंग कोड के Event Dispatch Thread (EDT) पर चलने पर; अधिकांश Swing ऑब्जेक्ट विधियों के थ्रेड-सुरक्षित न होने पर, ताकि कई थ्रेड से कॉल थ्रेड हस्तक्षेप और मेमोरी संगति त्रुटियाँ आमंत्रित करें, अर्थात् Swing घटकों तक पहुँच नियमतः EDT पर होनी चाहिए; अन्य थ्रेड से SwingUtilities.invokeLater / invokeAndWait से EDT पर कार्य माँगने पर; और EDT पर चलने वाले कार्यों के शीघ्र समाप्त होने की आवश्यकता पर।  2

  8. Oracle, ConcurrentHashMap (Java SE 21 & JDK 21 API). computeIfAbsent की पूरी विधि कॉल अणु रूप से निष्पादित होने, कुंजी अनुपस्थित होने पर मैपिंग फ़ंक्शन ठीक एक बार बुलाए जाने पर; गणना के दौरान अन्य थ्रेड की कुछ अद्यतन संक्रियाएँ अवरुद्ध होने पर, इसलिए उसे छोटा और सरल रखना चाहिए; मैपिंग फ़ंक्शन के इस मैप को स्वयं बदलने पर प्रतिबंध, पहचाने गए पुनरावर्ती अद्यतन के IllegalStateException पर; और प्राप्ति संक्रिया (get) के अवरुद्ध न होने, किसी कुंजी के अद्यतन और बाद की प्राप्ति के बीच happens-before संबंध होने पर।  2 3

  9. Oracle, The jstack Command (Java SE 21 Tools Reference). jstack के निर्दिष्ट Java प्रक्रिया के हर थ्रेड के स्टैक ट्रेस (कक्षा नाम, विधि नाम, पंक्ति संख्या) छापने पर; -l विकल्प के अतिरिक्त लॉक जानकारी सहित विस्तृत प्रदर्शन सक्षम करने पर; और jcmd जैसे अन्य निदान औज़ारों के साथ इस्तेमाल होने पर। 

  10. OpenJDK, JEP 505: Structured Concurrency (Fifth Preview). संरचित समवर्तीता API के संबंधित उपकार्यों के समूह को एक कार्य इकाई मानने और त्रुटि प्रसार तथा रद्दीकरण संरचित करने पर; StructuredTaskScope के स्थैतिक फ़ैक्टरी विधि (open) से खोले जाने वाले रूप में संशोधित होने पर; और JDK 25 तक इसके पाँचवाँ पूर्वावलोकन होने, अभी अंतिम सुविधा न होने पर। 

  11. OpenJDK, JEP 525: Structured Concurrency (Sixth Preview). संरचित समवर्तीता के JDK 26 में भी छठे पूर्वावलोकन के रूप में जारी रहने पर — अर्थात् अगस्त 2026 तक वर्तमान JDK में अभी पूर्वावलोकन सुविधा, इस्तेमाल के लिए पूर्वावलोकन सुविधाएँ सक्षम करनी पड़ती हैं। 

  12. OpenJDK, JEP 506: Scoped Values. Scoped Values के JDK 25 में अंतिम होने पर; और उनके थ्रेडों के भीतर तथा पार अपरिवर्तनीय संदर्भ डेटा सुरक्षित और कुशलता से साझा करने के तंत्र होने, ThreadLocal की समस्याओं (परिवर्तनीयता, जीवनचक्र प्रबंधन, और वंशानुगत लागत) का समाधान देने पर। 

निकटवर्ती विषयों में गहराई से जाने के लिए समान टैग वाले नवीनतम लेख।

व्यावहारिक मल्टीथ्रेडिंग सर्वोत्तम अभ्यास: C संस्करण — Win32 API तरीके से सुरक्षित लिखना

Win32 के साथ C में मल्टीथ्रेडिंग का स्थापित तरीका _beginthreadex से थ्रेड बनाना, SRW लॉक और कंडीशन वेरिएबल, Interlocked फ़ंक्शन, और स्टॉप...

व्यावहारिक मल्टीथ्रेडिंग सर्वोत्तम प्रथाएँ: C++ संस्करण — RAII और jthread से संरचना द्वारा दुर्घटनाएँ मिटाना

C++ में मल्टीथ्रेडिंग वह संसार है जहाँ डेटा रेस अपरिभाषित व्यवहार बन जाता है। यह लेख std::thread के डिस्ट्रक्टर का जाल, jthread और stop_t...

DllMain और Loader Lock — DLL आरंभीकरण में "कुछ न करें" कहे जाने का वास्तविक कारण

DllMain से LoadLibrary क्यों न बुलाएँ या दूसरे थ्रेड से सिंक्रोनाइज़ न करें। प्राथमिक स्रोतों से यह लेख समझाता है कि लोडर लॉक हर DLL सूचन...

"Not Responding" वास्तव में क्या है — Windows ऐप के हैंग का निर्णय कैसे करता है, और न हैंग करने वाले ऐप कैसे डिज़ाइन करें

Windows का "Not Responding" वह तंत्र है जिसमें OS तय करता है कि विंडो ने 5 सेकंड संदेश नहीं लिया और उसे घोस्ट विंडो से बदल देता है। यह ले...

ये पृष्ठ विषय को सेवाओं और निर्णयों के व्यापक संदर्भ में रखते हैं।

अक्सर पूछे जाने वाले प्रश्न

इस लेख के विषय पर परामर्श में अक्सर पूछे जाने वाले प्रश्न।

अब वर्चुअल थ्रेड हैं, तो क्या थ्रेड पूल (ExecutorService) की अब ज़रूरत नहीं?
यह उपयोग पर निर्भर करता है। वर्चुअल थ्रेड बहुत बड़ी संख्या में I/O-प्रतीक्षा प्रधान कार्यों को चलाने का तंत्र हैं; वे कोड तेज़ नहीं करते, वे थ्रूपुट बढ़ाते हैं। I/O-बाउंड काम के लिए प्रति कार्य एक वर्चुअल थ्रेड इस्तेमाल करें (Executors.newVirtualThreadPerTaskExecutor) और वर्चुअल थ्रेड को कभी पूल न करें। दूसरी ओर, CPU को पूरा भरने वाली गणना के समांतरण के लिए, लगभग कोर संख्या तक सीमित प्लेटफ़ॉर्म थ्रेड का पूल (या parallel stream) पहले की तरह सही औज़ार है। और यदि आप किसी बाहरी सेवा से समवर्ती कनेक्शन की संख्या सीमित करना चाहते हैं, तो वर्चुअल-थ्रेड युग की सिफ़ारिश पूल आकार से नहीं बल्कि Semaphore से सीमित करना है।
synchronized इस्तेमाल करूँ या ReentrantLock?
छोटे, सरल बहिष्करण के लिए synchronized काफी है, और कोड संक्षिप्त रहता है। ReentrantLock तब चुनें जब tryLock से समयबद्ध प्राप्ति, निष्पक्षता नीति, या कई Condition जैसी सुविधाएँ चाहिए। वर्चुअल थ्रेड के साथ जोड़ने पर एक ऐतिहासिक सावधानी है: JDK 21-23 में समस्या थी कि synchronized ब्लॉक के अंदर अवरोधन वर्चुअल थ्रेड को उसके OS थ्रेड से पिन कर देता था, इसलिए बार-बार या लंबे अवरोधन स्थलों को ReentrantLock से बदलने की सिफ़ारिश थी। JDK 24 (JEP 491) ने मॉनिटर कार्यान्वयन फिर लिखा और यह बाधा हल कर दी। JDK 24 से पिनिंग के कारण synchronized बदलने की ज़रूरत नहीं।
volatile जोड़ने से कुछ थ्रेड-सुरक्षित हो जाता है क्या?
नहीं। Java का volatile उस चर पर लिखने और पढ़ने के बीच happens-before संबंध बनाता है, दृश्यता (कि नवीनतम लेखन अन्य थ्रेड को दिखे) और क्रम की गारंटी देता है, पर «पढ़ो, गणना करो, वापस लिखो» जैसी संयुक्त संक्रिया की अणुता की गारंटी नहीं देता। कई थ्रेड से volatile int काउंटर पर ++ करें तो जोड़ खो जाते हैं। काउंटर के लिए AtomicInteger / AtomicLong (उच्च-आवृत्ति संकलन के लिए LongAdder) इस्तेमाल करें, और कई चर एक साथ सुरक्षित करने पर लॉक। volatile लगभग केवल सरल स्थिति-ध्वज जैसी स्थितियों में उपयुक्त है — जहाँ एक थ्रेड लिखता है और बाकी केवल पढ़ते हैं।
InterruptedException पकड़कर अनदेखा कर सकते हैं?
नहीं। Interruption Java का मानक रुकने और रद्द करने का संकेत है, और उसे निगलना एक ऐसा थ्रेड बनाता है जो रुकेगा नहीं। InterruptedException फेंके जाने तक interrupt स्थिति पहले ही साफ़ हो चुकी होती है, इसलिए यदि आप काम स्वयं पूरा नहीं कर सकते, तो Thread.currentThread().interrupt() से स्थिति पुनर्स्थापित करें ताकि कॉलर के लिए संकेत रहे, या अपवाद ज्यों-का-त्यों ऊपर फेंकें। खाली catch ब्लॉक जो कुछ नहीं करता, उन बगों का क्लासिक कारण है जहाँ शटडाउन लागू नहीं होता या shutdownNow अनदेखा हो जाता है।
Thread.stop से थ्रेड नहीं रोक सकते?
नहीं, नहीं रोक सकते। Thread.stop मूलतः असुरक्षित है (लॉक असंगत अवस्था में छोड़कर मुक्त करता है, टूटे ऑब्जेक्ट अन्य थ्रेड को दिखाता है), इसलिए लंबे समय से हतोत्साहित है, और वर्तमान Java में कॉल करने पर UnsupportedOperationException फेंकता है। Thread.suspend / resume पर भी यही लागू होता है। थ्रेड रोकने का एकमात्र वैध तरीका interruption से सहयोगी रोक है। यदि ExecutorService इस्तेमाल कर रहे हैं, तो shutdown केवल नए कार्य स्वीकारना बंद करता है और पूर्णता की प्रतीक्षा करता है — चल रहे कार्यों को interrupt नहीं भेजता। चल रहे कार्यों को रोकने की कोशिश shutdownNow करता है, और वह best-effort है (मानक कार्यान्वयन interruption से काम करता है)।

लेखक की प्रोफ़ाइल

लेख के लेखक का परिचय पृष्ठ।

Go Komura

KomuraSoft LLC के प्रतिनिधि

Windows सॉफ़्टवेयर विकास, तकनीकी परामर्श और बग जाँच में विशेषज्ञ, विशेष रूप से मौजूदा सिस्टम वाली परियोजनाओं और पुनरुत्पादन में कठिन बग में।

सार्वजनिक लिंक

ब्लॉग पर लौटें