implements Runnable vs extends Thread: لماذا يخطئ 90% من مطوري Java؟
\n\nتخيل أنك في مقابلة عمل بإحدى شركات التقنية الكبرى. يبتسم مسؤول الموارد البشرية ويطرح سؤالاً "بسيطاً": "ما الفرق بين implements Runnable و extends Thread في Java؟". قد يبدو الأمر سهلاً، لكن وفقاً لبيانات Stack Overflow، يتعثر حتى المطورون ذوو الخبرة هنا. آلاف المشاهدات ومئات التعليقات — موضوع "Java Runnable vs Thread" يظل من أكثر المواضيع إثارة للجدل.
\n\nفي هذا المقال، لن نكتفي بسرد الفروقات فقط. بل سنحلل أهم 5 أخطاء حقيقية يقع فيها المبرمجون، ونوضح لك كيفية كتابة كود تعدد مهام نظيف وقابل للتوسع. إذا كنت تكتب بلغة Java وتستخدم Thread مباشرة — اقرأ بتركيز. فقد تكون تخلق مشاكل لنفسك دون أن تدري.
مقدمة سريعة: ما هما Thread و Runnable؟
\n\nقبل أن نتعمق، دعنا نراجع الأساسيات. Java Thread هو كلاس يمثل مسار تنفيذ. يمكنه تشغيل كود بشكل متوازٍ. Runnable هو واجهة وظيفية (Functional Interface) تحتوي على دالة واحدة فقط هي run(). تصف المهمة التي يجب تنفيذها، لكنها لا تعرف كيفية تشغيلها.
إليك مثال تقليدي باستخدام extends Thread:
public class MyThread extends Thread {\n @Override\n public void run() {\n System.out.println("الـ Thread يعمل: " + Thread.currentThread().getName());\n }\n\n public static void main(String[] args) {\n MyThread t = new MyThread();\n t.start(); // تشغيل Thread جديد\n }\n}\n\nوهذا نفس الوظيفة باستخدام implements Runnable:
public class MyTask implements Runnable {\n @Override\n public void run() {\n System.out.println("المهمة تُنفذ: " + Thread.currentThread().getName());\n }\n\n public static void main(String[] args) {\n Thread t = new Thread(new MyTask());\n t.start();\n }\n}\n\nللوهلة الأولى، الفرق بسيط. لكن الشيطان، كما يُقال، يكمن في التفاصيل.
\n\nأهم 5 أخطاء عند العمل مع Thread و Runnable
\n\nالخطأ رقم 1: وراثة Thread عندما تحتاج إلى Runnable
\n\nالمشكلة الأكثر شيوعاً هي الارتباط الصارم بكلاس Thread. عندما تكتب extends Thread، يصبح كلاسك هو نفسه Thread. وبالتالي تفقد القدرة على وراثة كلاس آخر. Java لغة ذات وراثة مفردة، وهذا قيد كبير.
مثال على كود سيء:
\n\npublic class DataProcessor extends Thread {\n // لم يعد بإمكانك extends JFrame, ArrayList أو أي كلاس مفيد آخر\n @Override\n public void run() {\n // معالجة البيانات\n }\n}\n\nالحل الصحيح: استخدم implements Runnable. إنها واجهة — يمكنك تطبيقها في أي كلاس يرث شيئاً آخر بالفعل.
public class DataProcessor extends BaseService implements Runnable {\n @Override\n public void run() {\n // معالجة البيانات\n }\n}\n\nالخطأ رقم 2: الخلط بين run() و start()
\n\nكلاسيكي. المبتدئون (وحتى غيرهم) غالباً ما يستدعون run() بدلاً من start(). الفرق جوهري: start() ينشئ Thread جديداً ويستدعي run() داخله. أما run() فيُنفذ في الـ Thread الحالي كدالة عادية.
مثال على الخطأ:
\n\nThread t = new Thread(new MyTask());\nt.run(); // يعمل، لكن في نفس الـ Thread! لا يوجد تعدد مهام.\n\nسيكون الإخراج في الـ Thread الرئيسي main، وليس في Thread جديد. هذا الخطأ صعب الاكتشاف لأن الكود لا يتعطل — لكنه ببساطة لا يفعل ما تتوقعه.
كيف تتجنبه: استخدم دائماً start() لتشغيل Thread جديد. إذا كنت في شك