إنشاء الخيوط
إنشاء الخيوط
في Java ثمة طريقتان كلاسيكيتان لتعريف وحدة العمل التي يُنفّذها الخيط: توسيع Thread أو تنفيذ Runnable. خيار ثالث — Callable مع Future — يُغطَّى في درس لاحق عن مجمّعات الخيوط. الآن نركّز على الأساسيات، والقاعدة الأهم التي يخطئ فيها المبتدئون يوميًا: استدعاء start() لا run().
الخيار الأول: توسيع Thread
لأن java.lang.Thread ينفّذ Runnable بذاته، يمكنك اشتقاق فئة منه وتجاوز run() مباشرةً:
يعمل الخيطان بالتزامن: ستتداخل أسطر مخرجات Thread-A وThread-B بترتيب غير حتمي.
run() مباشرةً أبدًا. استدعاء t1.run() بدلًا من t1.start() لا يُنشئ خيطًا جديدًا — إنه استدعاء دالة عادي على الخيط الحالي، تمامًا كأي دالة أخرى. لا تزامن يحدث على الإطلاق. هذا من أكثر الأخطاء شيوعًا في كود التزامن للمبتدئين.
الخيار الثاني: تنفيذ Runnable
Runnable واجهة وظيفية (دالة مجردة واحدة: void run()). تُمرّر نسخةً منها إلى مُنشئ Thread:
لأن Runnable واجهة وظيفية، يمكنك كتابة المهمة مباشرةً بـ lambda — دون فئة منفصلة:
start() مقابل run() — الآلية الداخلية
فهم ما يفعله start() فعلًا يحول دون فئة كاملة من الأخطاء:
start()يطلب من JVM تخصيص خيط نظام تشغيل أصلي وجدولته ثم استدعاءrun()على ذلك الخيط الجديد. يعود الخيط المُستدعي فورًا منstart()ويكمل عمله الخاص.run()مجرد دالة Java عادية. استدعاؤها مباشرةً ينفّذ المنطق على خيط المُستدعي بالتسلسل، دون أي تزامن.- استدعاء
start()على خيط بدأ مسبقًا يُلقيIllegalThreadStateException. كائنThreadللاستخدام مرة واحدة فقط.
توسيع Thread مقابل تنفيذ Runnable — المقايضات
كلا الأسلوبين يُنشئان خيوطًا، لكنهما يختلفان في التصميم:
- توسيع
Thread— سهل في الأمثلة الصغيرة، لكن Java تدعم الوراثة المفردة فقط. إن كانت فئتك تمتدّ من شيء آخر فهذا الباب مغلق. كما أنه يربط منطق المهمة بآلية الخيط بإحكام. - تنفيذ
Runnable— يفصل ماذا نفعل عن كيف يُجدوَل. يمكن تقديم نفسRunnableلمجمّع خيوط (ExecutorService)، أو تشغيله مباشرةً في الاختبارات، أو تغليفه في خيط افتراضي (Java 21). هذه المرونة هي السبب في أنRunnableهو الاختيار الصحيح دائمًا تقريبًا في كود الإنتاج.
Runnable (أو lambda) للمهام؛ واستخدم فئة Thread فقط حين تحتاج تحديدًا لضبط خصائص الخيط (setDaemon، setName، setPriority) أو حين تمدّها لبناء نوع خيط متخصّص كعامل في مجمّع. في كود التطبيقات الحديث لن تمدّ Thread بالكاد أبدًا.
تسمية الخيوط
أعطِ الخيوط دائمًا أسماءً ذات معنى. الأسماء الافتراضية (Thread-0، Thread-1، …) عديمة الفائدة عند قراءة تفريغ خيوط أثناء حادثة في الإنتاج:
مع خيط مسمّى، يخبرك تتبّع المكدس فورًا بـأي مكوّن منطقي عالق أو يستنزف المعالج.
الخلاصة
ثمة طريقتان كلاسيكيتان لإنشاء الخيوط: توسيع Thread (تجاوز run()) أو تنفيذ Runnable (تمريره لمنشئ Thread). استدعِ start() دائمًا للحصول على خيط جديد — run() مجرد استدعاء دالة. فضّل Runnable أو lambda في كود الإنتاج لأنها تفصل المهمة عن آلية التنفيذ، وتبقي تسلسل الوراثة مفتوحًا، وتعمل بسلاسة مع ExecutorService والخيوط الافتراضية. في الدرس التالي سنرى الدورة الحياتية الكاملة للخيط من الإنشاء إلى الإنهاء.