Starvation (थ्रेड स्टार्वेशन) — एक ऐसी स्थिति है जिसमें थ्रेड कार्य जारी रखने के लिए आवश्यक संसाधन तक पहुंच प्राप्त नहीं कर पाता, भले ही वह निष्पादन के लिए तैयार हो। Baeldung (Java Thread Starvation, 2024) के अनुसार, स्टार्वेशन अनुचित शेड्यूलिंग के कारण उत्पन्न होता है, जब निम्न-प्राथमिकता वाले थ्रेड को उच्च-प्राथमिकता वाले थ्रेड के पक्ष में लगातार स्थगित किया जाता है। Deadlock के विपरीत, Starvation थ्रेड को ब्लॉक नहीं करता — वह RUNNABLE अवस्था में रहता है लेकिन कभी CPU समय प्राप्त नहीं करता।
मुख्य बातें
Starvation (थ्रेड स्टार्वेशन) — एक मल्टीथ्रेडिंग समस्या है जहाँ एक थ्रेड अपने कार्य को पूरा करने के लिए आवश्यक संसाधन तक पहुंच प्राप्त नहीं कर पाता, भले ही संसाधन किसी अन्य थ्रेड द्वारा स्थायी रूप से लॉक न किया गया हो। थ्रेड RUNNABLE अवस्था में है, लेकिन शेड्यूलर या सिंक्रोनाइज़ेशन तंत्र व्यवस्थित रूप से अन्य थ्रेड के पक्ष में इसके निष्पादन को स्थगित करता है।
मोबाइल डेवलपमेंट में, Starvation असमान कार्य निष्पादन के रूप में प्रकट होता है: कुछ ऑपरेशन तुरंत निष्पादित होते हैं, जबकि अन्य भयावह देरी से ग्रस्त होते हैं। उदाहरण के लिए, एक पृष्ठभूमि डेटा सिंक्रोनाइज़ेशन थ्रेड कभी भी डेटाबेस तक पहुंच प्राप्त नहीं कर सकता यदि UI थ्रेड और एनिमेशन हैंडलर लगातार इसे आगे निकल जाते हैं। Android Developer Blog (Performance Matters, 2023) के अनुसार, Android पर लगभग 12% मिस्ड फ्रेम (jank) रेंडरिंग पर निर्भर पृष्ठभूमि कार्यों के Starvation के कारण होते हैं।
Starvation और Deadlock के बीच मुख्य अंतर प्रतिवर्तीता है। यदि सिस्टम लोड कम हो जाता है या प्राथमिकताएं पुनर्वितरित हो जाती हैं, तो भूखा थ्रेड संसाधन प्राप्त कर सकता है और अपना कार्य पूरा कर सकता है। हालांकि, निरंतर उच्च लोड के तहत, Starvation अनिश्चित काल तक रह सकता है, जिससे एप्लिकेशन के फ्रोजन होने का आभास होता है।
Java और Kotlin में synchronized अनुचित तंत्र का एक उत्कृष्ट उदाहरण है। उच्च प्रतिस्पर्धा के तहत, JVM लगातार उन्हीं सक्रिय थ्रेड को लॉक प्रदान कर सकता है, जबकि अन्य थ्रेड लगातार दौड़ हार जाते हैं। यह JVM की बग नहीं बल्कि डिज़ाइन ट्रेड-ऑफ है: अनुचित लॉक पहुंच की निष्पक्षता की कीमत पर उच्च थ्रूपुट प्रदान करते हैं। 4–8 थ्रेड वाले मोबाइल एप्लिकेशन के लिए, यह समस्या विशेष रूप से प्रासंगिक है।
अलग-अलग थ्रेड प्राथमिकताएं सेट करने से निम्न-प्राथमिकता वाले थ्रेड का Starvation हो सकता है। Android Runtime में, Linux CFS (Completely Fair Scheduler) शेड्यूलर CPU समय को प्राथमिकताओं के अनुपात में वितरित करता है, और यदि उच्च-प्राथमिकता वाले थ्रेड लगातार सक्रिय हैं, तो निम्न-प्राथमिकता वाले थ्रेड को कभी CPU समय नहीं मिल सकता है। Google Android में थ्रेड प्राथमिकताएं बदलने से सख्ती से मना करता है — सिस्टम स्वयं उनका प्रबंधन करता है।
यदि कोई थ्रेड बहुत लंबे समय तक लॉक रखता है (synchronized ब्लॉक के अंदर भारी गणना, नेटवर्क अनुरोध या फ़ाइल संचालन करता है), तो उस लॉक की प्रतीक्षा करने वाले अन्य थ्रेड भूखे रह जाते हैं। यह Android में विशेष रूप से खतरनाक है, जहां UI थ्रेड पर लंबे संचालन ANR का कारण बनते हैं, और क्रिटिकल सेक्शन को ऑप्टिमाइज़ किए बिना उन्हें पृष्ठभूमि थ्रेड में ले जाना केवल Starvation समस्या को वर्कर थ्रेड में स्थानांतरित करता है।
एक उदाहरण पर विचार करें जहाँ अनुचित शेड्यूलिंग के कारण एक थ्रेड बहुत बार लॉक प्राप्त करता है। Starvation को एक उच्च-प्राथमिकता वाले थ्रेड के अनंत लूप के माध्यम से प्रदर्शित किया जाता है जो निम्न-प्राथमिकता वाले थ्रेड को साझा संसाधन तक पहुंचने से रोकता है।
class SharedResource {
private val lock = Any()
fun criticalSection(id: String) {
synchronized(lock) {
println("$id को पहुंच मिली")
Thread.sleep(10) // कार्य का अनुकरण
}
}
}
fun main() {
val resource = SharedResource()
// उच्च-प्राथमिकता वाला थ्रेड — लगातार सक्रिय
val highPriority = Thread {
while (true) {
resource.criticalSection("High")
}
}
// निम्न-प्राथमिकता वाला थ्रेड — कभी पहुंच नहीं मिल सकती
val lowPriority = Thread {
while (true) {
resource.criticalSection("Low")
}
}
highPriority.start()
lowPriority.start()
// "Low" कभी संदेश प्रिंट नहीं कर सकता — Starvation!
}
इस उदाहरण में, highPriority थ्रेड लगातार लॉक प्राप्त करता है और इसे केवल 10 ms के लिए छोड़ता है। synchronized की अनुचित प्रकृति के कारण, JVM शेड्यूलर सबसे अधिक संभावना उसी थ्रेड को फिर से लॉक प्रदान करेगा जिसने अभी-अभी इसे छोड़ा है — निम्न-प्राथमिकता वाला थ्रेड भूखा रह जाता है। समाधान ReentrantLock(true) का उपयोग fair फ्लैग के साथ करना है, जो FIFO प्रतीक्षा क्रम सुनिश्चित करता है।
फेयर लॉक के साथ सही किया गया संस्करण संसाधन तक निष्पक्ष पहुंच सुनिश्चित करता है।
class FairSharedResource {
private val fairLock = ReentrantLock(true) // fair = true
fun criticalSection(id: String) {
fairLock.lock()
try {
println("$id को पहुंच मिली (fair)")
Thread.sleep(10)
} finally {
fairLock.unlock()
}
}
}
तीन क्लासिक मल्टीथ्रेडिंग समस्याएं — Starvation, Deadlock और Livelock — अक्सर एक साथ समूहित की जाती हैं, लेकिन उनके तंत्र और समाधान भिन्न होते हैं। Starvation — थ्रेड तैयार है लेकिन संसाधन प्राप्त नहीं कर सकता। Deadlock — थ्रेड चक्रीय प्रतीक्षा द्वारा अवरुद्ध हैं। Livelock — थ्रेड सक्रिय हैं लेकिन कोई प्रगति नहीं करते।
| पैरामीटर | Starvation | Deadlock | Livelock |
|---|---|---|---|
| थ्रेड अवस्था | RUNNABLE | BLOCKED | RUNNABLE |
| प्रगति | कोई नहीं | कोई नहीं | कोई नहीं (हालांकि सक्रिय) |
| CPU उपयोग | कम | न्यूनतम | उच्च (100% तक) |
| कारण | अनुचित शेड्यूलिंग | चक्रीय प्रतीक्षा | संघर्ष पर समान प्रतिक्रिया |
| मुख्य सुधार | Fair Lock, क्रिटिकल सेक्शन छोटा करना | लॉक पदानुक्रम | पुनः प्रयास सीमा, exponential backoff |
Starvation को Deadlock से कम गंभीर माना जाता है क्योंकि यह घातक नहीं है — कम लोड के तहत, भूखा थ्रेड अंततः निष्पादित होगा। हालांकि, वास्तविक Android उपयोग में, जहाँ मेमोरी और CPU सीमित हैं, Starvation मिनटों तक रह सकता है, जो एक अस्वीकार्य UX बनाता है।
Thread Dump को छोटे अंतराल पर बार-बार लेना — स्टार्वेशन का पता लगाने की मूल विधि है। यदि कोई थ्रेड लगातार RUNNABLE अवस्था में है लेकिन उसका कॉल स्टैक कई डंप में नहीं बदलता — यह Starvation का एक क्लासिक संकेत है। Android Studio में, समय के साथ थ्रेड स्थिति रिकॉर्डिंग के लिए Android Profiler का उपयोग करें।
कार्यों के निष्पादन समय की निगरानी के माध्यम से स्वचालित पहचान संभव है। यदि एक अनुमानित निष्पादन समय (जैसे, 50 ms) वाला कार्य 5 सेकंड या उससे अधिक लेता है — Starvation की उच्च संभावना है। मोबाइल एप्लिकेशन में, Firebase Performance Monitoring आपको क्रिटिकल सेक्शन के लिए कस्टम ट्रेस सेट करने और सीमा पार होने पर सूचनाएं प्राप्त करने की अनुमति देता है।
synchronized ब्लॉक के कारण होने वाले Starvation के निदान के लिए, Java Flight Recorder (JFR) (OpenJDK API के माध्यम से Android पर उपलब्ध) या Async Profiler का उपयोग करें। ये उपकरण दिखाते हैं कि किन मॉनिटरों में सबसे अधिक प्रतीक्षा समय है और कौन से थ्रेड प्रत्येक मॉनिटर के लिए प्रतिस्पर्धा कर रहे हैं। JFR डेटा अपने अंतर्निहित प्रोफाइलर के माध्यम से IntelliJ IDEA Ultimate के साथ एकीकृत होता है।
ReentrantLock(true) सुनिश्चित करता है कि थ्रेड FIFO क्रम में लॉक प्राप्त करें। synchronized के विपरीत, एक फेयर लॉक उस थ्रेड को तुरंत लॉक पुनः प्राप्त करने की अनुमति नहीं देता जिसने अभी-अभी इसे छोड़ा है। यह Starvation को पूरी तरह से समाप्त करता है, हालांकि कतार बनाए रखने के ओवरहेड के कारण कुल थ्रूपुट 10–20% कम हो जाता है।
लॉक-फ्री डेटा संरचनाएं (ConcurrentHashMap, AtomicReference, LongAdder) परिभाषा के अनुसार Starvation को समाप्त करती हैं, क्योंकि उनमें ऐसे लॉक नहीं होते जो एक थ्रेड द्वारा रखे जा सकें। सभी ऑपरेशन CPU CAS निर्देशों का उपयोग करते हैं जो सीमित चरणों में कम से कम एक थ्रेड की प्रगति सुनिश्चित करते हैं। मोबाइल डेवलपमेंट के लिए, कार्य कतारों के लिए ConcurrentLinkedQueue को प्राथमिकता दें।
लॉक धारण समय को कम करना Starvation के जोखिम को कम करने का एक सार्वभौमिक तरीका है। भारी संचालन (नेटवर्क, डिस्क I/O, जटिल गणना) को synchronized ब्लॉक के बाहर ले जाएं। उन परिदृश्यों के लिए ReadWriteLock का उपयोग करें जहाँ पाठकों को दुर्लभ लेखकों के कारण भूखा नहीं रहना चाहिए। Kotlin Coroutines लाइब्रेरी एक suspending तंत्र के साथ Mutex प्रदान करती है जो OS थ्रेड को ब्लॉक नहीं करता।
Condition.await() और signal() का उपयोग सावधानी से किया जाना चाहिए: Condition पर प्रतीक्षा करने वाला थ्रेड अन्य थ्रेड के साथ जागता है (spurious wakeup), और सभी लॉक के लिए प्रतिस्पर्धा करते हैं। यदि एक थ्रेड await के तुरंत बाद प्रतीक्षा में लौट आता है जबकि अन्य लॉक प्राप्त करने में सफल हो जाते हैं, तो भूखा थ्रेड अनिश्चित काल तक जाग और सो सकता है। पुनः जांच सुनिश्चित करने के लिए हमेशा if के बजाय while लूप में स्थिति की जांच करें।
अक्सर पूछे जाने वाले प्रश्न
Priority Inversion — एक ऐसी स्थिति है जहाँ निम्न-प्राथमिकता वाला थ्रेड एक लॉक रखता है जिसकी उच्च-प्राथमिकता वाले थ्रेड को आवश्यकता होती है। परिणामस्वरूप, उच्च-प्राथमिकता वाला थ्रेड निम्न-प्राथमिकता वाले की प्रतीक्षा करता है — प्राथमिकताएं उलट जाती हैं। Starvation एक व्यापक समस्या है: थ्रेड प्राथमिकता की परवाह किए बिना, अनुचित शेड्यूलिंग या लंबे क्रिटिकल सेक्शन के कारण संसाधन प्राप्त नहीं कर पाता।
नहीं, Starvation एक मल्टीथ्रेडिंग समस्या है। एकल-थ्रेड कोड में संसाधन प्रतिस्पर्धा या थ्रेड शेड्यूलिंग नहीं होती। हालांकि, Starvation अतुल्यकालिक एकल-थ्रेड कोड (उदाहरण के लिए, JavaScript इवेंट लूप) में हो सकता है यदि एक माइक्रोटास्क शून्य विलंब के साथ setTimeout के माध्यम से दूसरों के निष्पादन को अनिश्चित काल तक स्थगित करता है।
JMM (Java Memory Model) थ्रेड के बीच परिवर्तनों की दृश्यता के नियमों को परिभाषित करता है लेकिन निष्पक्ष शेड्यूलिंग की गारंटी नहीं देता। synchronized, JMM के अनुसार, अनुक्रमिक स्थिरता — बुनियादी शुद्धता — सुनिश्चित करता है, लेकिन Starvation को नहीं रोकता। निष्पक्षता के लिए JMM में निर्दिष्ट नहीं किए गए अतिरिक्त तंत्रों की आवश्यकता होती है।
UI थ्रेड (Main Thread) शास्त्रीय अर्थों में भूखा नहीं रह सकता क्योंकि इसकी सर्वोच्च प्राथमिकता है। हालांकि, Starvation तब होता है जब UI थ्रेड भूखे पृष्ठभूमि थ्रेड से परिणाम की प्रतीक्षा करता है। एक विशिष्ट परिदृश्य: AsyncTask या कोरूटीन डेटा लोड करता है लेकिन अन्य थ्रेड के साथ प्रतिस्पर्धा के कारण डेटाबेस तक पहुंच प्राप्त नहीं कर पाता, और UI प्रतीक्षा में फ्रीज हो जाता है।
कोरूटीन में, Starvation को रोकने के लिए, थ्रेड समाप्ति से बचने के लिए Dispatchers.IO पर limitedParallelism का उपयोग करें। सिंक्रोनाइज़ेशन के लिए, kotlinx.coroutines.sync से Mutex का उपयोग करें — यह थ्रेड को ब्लॉक करने के बजाय कोरूटीन को निलंबित करता है, जिससे स्टार्वेशन का जोखिम कम होता है। कोरूटीन में runBlocking से बचें, क्योंकि यह पूल थ्रेड पर कब्जा कर सकता है और अन्य कोरूटीन के Starvation का कारण बन सकता है।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें