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 نظام الاوقات زیادہ تر امکان ہے کہ لاک دوبارہ اسی تھریڈ کو دے گا جس نے ابھی اسے چھوڑا ہے — کم ترجیح والا تھریڈ بھوکا رہ جاتا ہے۔ حل fair جھنڈے کے ساتھ ReentrantLock(true) استعمال کرنا ہے، جو 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 منٹوں تک جاری رہ سکتا ہے، جو ایک ناقابل قبول صارف تجربہ پیدا کرتا ہے۔
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 لائبریری ایک معطل کرنے والے طریقہ کار کے ساتھ Mutex فراہم کرتی ہے جو OS تھریڈ کو مسدود نہیں کرتا۔
Condition.await() اور signal() احتیاط سے استعمال کرنا چاہیے: Condition پر انتظار کرنے والا تھریڈ دوسرے تھریڈ کے ساتھ جاگتا ہے (spurious wakeup)، اور سب لاک کے لیے مقابلہ کرتے ہیں۔ اگر ایک تھریڈ await کے فوراً بعد انتظار میں واپس آ جاتا ہے جبکہ دوسرے لاک حاصل کرنے میں کامیاب ہو جاتے ہیں، تو بھوکا تھریڈ غیر معینہ مدت تک جاگ اور سو سکتا ہے۔ دوبارہ جانچ کو یقینی بنانے کے لیے ہمیشہ if کے بجائے while لوپ میں شرط چیک کریں۔
اکثر پوچھے گئے سوالات
Priority Inversion (ترجیح الٹنا) ایک ایسی صورت حال ہے جہاں کم ترجیح والا تھریڈ ایک لاک رکھتا ہے جس کی زیادہ ترجیح والے تھریڈ کو ضرورت ہے۔ نتیجے کے طور پر، زیادہ ترجیح والا تھریڈ کم ترجیح والے کا انتظار کرتا ہے — ترجیحات الٹ جاتی ہیں۔ Starvation ایک وسیع تر مسئلہ ہے: تھریڈ ترجیح سے قطع نظر، غیر منصفانہ نظام الاوقات یا لمبے اہم حصوں کی وجہ سے وسائل حاصل نہیں کر سکتا۔
نہیں، Starvation ایک ملٹی تھریڈنگ مسئلہ ہے۔ ایک تھریڈ والے کوڈ میں وسائل کا مقابلہ یا تھریڈ نظام الاوقات نہیں ہوتا۔ تاہم، غیر متزامن ایک تھریڈ والے کوڈ (مثلاً، JavaScript ایونٹ لوپ) میں Starvation ہو سکتا ہے اگر ایک مائیکرو ٹاسک صفر تاخیر کے ساتھ 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 ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں