دورة حياة الخيط
دورة حياة الخيط
فهم ما يفعله الخيط في أي لحظة أمرٌ أساسي لكتابة كود متزامن صحيح. تُصنّف Java وجود الخيط على شكل آلة ذات حالات محدودة: ينتقل الخيط عبر مجموعة من الحالات المُعرَّفة تعريفًا واضحًا، من لحظة إنشائه حتى انتهائه. معرفة هذه الحالات — والأساليب التي تُحدث الانتقال بينها — تُمكّنك من استيعاب سلوك البرنامج، وتشخيص حالات التعليق، وكتابة منطق تنسيق يعمل فعليًا.
الحالات الست للخيط
يُعرّف تعداد Thread.State (المُقدَّم في Java 5 والمستمر حتى Java 17+) ستّ حالات:
- NEW — تم إنشاء كائن
Threadلكن لم يُستدعَstart()بعد. لا يوجد خيط نظام تشغيل حتى الآن. - RUNNABLE — بدأ تشغيل الخيط. إمّا أنه يعمل فعليًا على نواة معالج الآن، أو أنه جاهز للتشغيل وينتظر من جدولة نظام التشغيل أن تمنحه نواة. لا تُميّز Java في تعداد الحالة بين "يعمل" و"جاهز للعمل".
- BLOCKED — الخيط ينتظر الحصول على قفل مراقب جوهري (كتلة
synchronizedأو أسلوب يحتجزه خيط آخر). لا يمكنه المتابعة حتى يُفرَج عن القفل. - WAITING — الخيط معلّق إلى أجل غير مسمى، ينتظر أن يتخذ خيطٌ آخر إجراءً محددًا. يحدث هذا عند استدعاء
Object.wait()، أوThread.join()بلا مهلة، أوLockSupport.park(). - TIMED_WAITING — مثل WAITING لكن بمدة زمنية قصوى. الأساليب التي تسبّب هذه الحالة:
Thread.sleep(millis)، وObject.wait(millis)، وThread.join(millis)، وLockSupport.parkNanos(). - TERMINATED — عاد أسلوب
run()الخاص بالخيط، أو أنهته استثناءٌ غير معالَج. كائنThreadلا يزال موجودًا في الذاكرة لكن لا يمكن إعادة تشغيله أبدًا.
يمكنك فحص الحالة الراهنة لخيط ما في وقت التشغيل باستخدام thread.getState(). هذا مفيد بصفة رئيسية للتشخيص والأدوات، لا للتحكم في التدفق.
sleep — إيقاف الخيط الحالي مؤقتًا
يجعل Thread.sleep(long millis) الخيطَ الذي يعمل حاليًا يدخل TIMED_WAITING لمدة لا تقل عن عدد الميلي ثانية المحدد، ثم يعود إلى RUNNABLE. إنه أسلوب ساكن (static) — لا يمكنك جعل خيط آخر ينام.
حقيقتان مهمتان عن sleep:
- لا يُفرج عن أقفال المراقب. إذا احتجز الخيط النائم قفل
synchronized، فإن الخيوط الأخرى المنتظرة لذلك القفل تبقى محجوبة (BLOCKED) طوال مدة النوم. هذا مصدر شائع لضعف الأداء. - المدة هي حدٌّ أدنى، وليست ضمانًا. قد يستأنف جدول نظام التشغيل الخيطَ بعد وقت أطول قليلًا من المطلوب، خاصة على نظام مُثقَل.
شغّل هذا وسترى أن المنتظر يبقى في حالة BLOCKED طوال 3 ثوانٍ كاملة.
join — انتظار انتهاء خيط آخر
يجعل thread.join() الخيطَ المُستدعي يدخل WAITING (أو TIMED_WAITING مع معامل المهلة) حتى يصل thread إلى TERMINATED. إنه الطريقة القياسية للانتظار حتى ينتهي عمل خلفي قبل استخدام نتائجه.
النسخة ذات المهلة worker.join(millis) تعود بعد انتهاء المهلة حتى لو كان العامل لا يزال يعمل — تحقق دائمًا من worker.isAlive() بعدها إذا كانت النتيجة مهمة.
join() على كل منها. مجموع الوقت الفعلي يقارب مدة أطول مهمة، لا مجموع المهام — إذ تعمل المهام كلها بالتوازي، وتنتظر الحلقة ببساطة كل واحدة بالتسلسل.
interrupt — طلب الإلغاء
نموذج الإلغاء في Java تعاوني. لا تُوقف الخيط قسرًا؛ بل تُعيّن علامة الإقطاع الخاصة به باستدعاء thread.interrupt()، ويُتوقع من الخيط أن يلاحظ ذلك ويتوقف طوعًا.
هناك طريقتان يلاحظ بهما الخيط علامة الإقطاع:
- الأساليب الحاجبة تُطلق
InterruptedException. عندما يكون خيط في حالة WAITING أو TIMED_WAITING (نائم، أو منضم، أو منتظر على مراقب عبرObject.wait()) ويستدعي خيطٌ آخرinterrupt()عليه، يُطلق الاستدعاء الحاجبInterruptedExceptionوتُمسَح علامة الإقطاع. - الاستطلاع باستخدام
Thread.interrupted()أوthread.isInterrupted(). يمكن للحلقات الطويلة المرتبطة بالمعالج أن تتحقق من العلامة دوريًا.
catch (InterruptedException e) { /* تجاهل */ } يمسح علامة الإقطاع دون التصرف بناءً عليها، مما يجعل الخيط غير استجابي للإلغاء. إمّا أعِد إطلاق الاستثناء (إذا أتاح توقيع الأسلوب ذلك)، أو أعِد تعيين العلامة باستخدام Thread.currentThread().interrupt() قبل الإرجاع.
النمط الصحيح لإعادة التعيين:
تجميع كل شيء: تفصيل انتقالات الحالة
تأمّل هذا التسلسل لخيط واحد:
new Thread(task)— الحالة NEW.thread.start()— تُنشئ JVM خيط نظام تشغيل؛ تصبح الحالة RUNNABLE.- داخل
task، يُستدعىThread.sleep(500)— تصبح الحالة TIMED_WAITING. - تنتهي مدة النوم — تعود الحالة إلى RUNNABLE.
- تحاول المهمة الدخول في كتلة
synchronizedيحتجزها خيط آخر — تصبح الحالة BLOCKED. - يُفرَج عن القفل — تعود الحالة إلى RUNNABLE.
- يُرجع
run()— تصبح الحالة TERMINATED.
الخلاصة
ينتقل خيط Java عبر ست حالات مُعرَّفة بوضوح: NEW → RUNNABLE → (BLOCKED / WAITING / TIMED_WAITING) → TERMINATED. الأساليب الرئيسية التي تحكم هذه الانتقالات هي Thread.sleep() (إيقاف طوعي دون الإفراج عن الأقفال)، وThread.join() (انتظار انتهاء خيط آخر)، وThread.interrupt() (إلغاء تعاوني عبر علامة). إتقان هذه الانتقالات هو أساس كل بنية تزامن ذات مستوى أعلى مُغطاة في بقية هذه الدورة التعليمية.