Javaالإنترفيس Runnable في جافا

مقدمة

المشكلة الوحيدة التي قد تواجهها عند إنشاء الـ Thread من خلال وراثة الكلاس Thread هي أن الكلاس يصبح غير قادر على أن يرث من كلاس آخر, لأنه لا يمكن للكلاس أن يفعل extends لأكثر من كلاس.

لحل مشكلة تعدد الوراثة, يمكنك جعل الكلاس يطبق الإنترفيس Runnable و بداخله نقوم بإنشاء كائن من الكلاس Thread و نربط به.


بناؤه

public interface Runnable
	

خطوات تطبيق الإنترفيس Runnable

الكلاس الذي يطبق الإنترفيس Runnable عليه القيام بالخطوات التالية:

  1. تطبيق الإنترفيس Runnable, أي أن يفعل له implements.

  2. كتابة محتوى الدالة run(), أي أن يفعل لها Override و يضع فيها جميع الأوامر التي يريدها أن تتنفذ عند تشغيل كائن الـ Thread.

مثال

MyRunnable.java
public class MyRunnable implements Runnable {

    @Override
    public void run(){
        // Thread من كائن من الكلاس start() هنا تضع الأوامر التي تريد تنفيذها عند إستدعاء الدالة
    }

}
		

خطوات إنشاء كائن من الكلاس Thread و ربطه بالكلاس الذي يطبق الإنترفيس Runnable

لإنشاء كائن من الكلاس الذي يطبق الإنترفيس Runnable و تنفيذ أوامره بشكل متوازي مع باقي الأوامر الموجودة في البرنامج, عليك إتباع الخطوات التالية:

  1. إنشاء كائن من الكلاس الذي يرث من الإنترفيس Runnable.

  2. إنشاء كائن من الكلاس Thread, و تمرير كائن من الكلاس الذي يطبق الإنترفيس Runnable كـ Argument له.

  3. إستدعاء الدالة start() لتشغيله.

  4. بعدها يمكنك التعامل معه كـ Thread عادي و التحكم بطريقة تنفيذ أوامره كما تشاء.

مثال

MainRunnable.java
public class MainRunnable {

    public static void main(Strigns[] args)){

        MyRunnable mr = new MyRunnable();   // MyRunnable هنا قمنا بإنشاء كائن من الكلاس الذي يطبق الإنترفيس

        Thread t = new Thread(mr);          // mr و ربطه بالكائن Thread هنا قمنا بإنشاء كائن من الكلاس

        t.start();                          // start() بواسطة الدالة run() هنا قمنا بتشغيل أوامر الدالة

    }

}
		

دوال الإنترفيس Runnable

الإنترفيس Runnable يملك الدالة التالية فقط.

public void run()
	

الكلاس الذي يطبق الإنترفيس Runnable يجب أن يفعل لها Override, و يضع بداخلها الأوامر التي يريدها أن تتنفذ عند تشغيل كائن الـ Thread.

لتنفيذ أوامر الدالة run() بشكل متوازي مع باقي الأوامر الموجودة في البرنامج, عليك إستدعاء الدالة start() من كائن الـ Thread المرتبط بالكلاس الذي يطبق الإنترفيس Runnable, و الذي يملك الدالة run().


تذكر: إستدعاء الدالة run() بشكل مباشر لا يجعلها تعمل بشكل متوازي مع البرنامج.

أمثلة شاملة


ملاحظة

هذه نفس الأمثلة التي وضعناها في شرح الكلاس Thread من حيث الأفكار, مع بعض التعديلات لجعل الكلاس يطبق الإنترفيس Runnable بدل أن يرث من الكلاس Thread.


المثال الأول

في المثال التالي قمنا بإنشاء برنامج يعرض الوقت الحالي طالما أن البرنامج شغال.

في البداية قمنا بإنشاء كلاس إسمه RealTime يطبق الإنترفيس Runnable.
بعدها فعلنا Override للدالة run() لجعلها تطبع الوقت الحالي.

ثم قمنا بإنشاء كلاس إسمه Main لتجربة هذا الـ Thread.
في الكلاس Main قمنا بإنشاء كائن من الكلاس Thread ربطناه بالكلاس RealTime, ثم قمنا بتشغيله بواسطة الدالة start().

إذاً عند تشغيل هذا البرنامج سيتم عرض الوقت الحالي طالما أن البرنامج شغال.

RealTime.java
import java.util.Date;		// Date هنا قمنا باستدعاء الكلاس
 
public class RealTime implements Runnable {
 
    @Override
    public void run() {
        // true لا ترجع isInterrupted() طالما أن الدالة
        while(!Thread.currentThread().isInterrupted())
        {
            // سيتم طباعة الوقت الحالي
            System.out.printf("Current time: %tr \n", new Date());
 
            // لثانية واحدة Thread بعدها سيتم إيقاف كائن الـ
            try {
                Thread.sleep(1000);
            }
            catch(Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
 
}
		

Main.java
public class Main {
 
    public static void main(String[] args) {
 
        // rt إسمه RealTime هنا قمنا بإنشاء كائن من الكلاس
        RealTime rt = new RealTime();
 
        // rt و ربطناه بالكائن t إسمه Thread هنا قمنا بإنشاء كائن من الكلاس
        Thread t = new Thread(rt);
 
        // لعرض الوقت t هنا قمنا بتشغيل الكائن
        t.start();
 
    }
 
}
		

عند تشغيل البرنامج, سيتم عرض الوقت الحالي كل ثانية كالتالي.

Current time: 09:02:15 AM
Current time: 09:02:16 AM
Current time: 09:02:17 AM
Current time: 09:02:18 AM
Current time: 09:02:19 AM
Current time: 09:02:20 AM
Current time: 09:02:21 AM
Current time: 09:02:22 AM
Current time: 09:02:23 AM
Current time: 09:02:24 AM
Current time: 09:02:25 AM
Current time: 09:02:26 AM
Current time: 09:02:27 AM
....
		


المثال الثاني

المثال التالي عبارة عن برنامج يقوم بإختبار قدرة المستخدم في العمليات الحسابية, فهو يقوم بخلق عمليات جمع عشوائية خلال مدة معينة, و إنتظار المستخدم للإجابة عليها, و في الأخير سيتم عرض النتيجة النهائية له.

في المثال التالي قمنا بإنشاء كلاس إسمه ExamTimer يطبق الإنترفيس Runnable.
بعدها فعلنا Override للدالة run() حتى نجعل أي كائن من الكلاس ExamTimer ينتظر مدة 20 ثانية بعد تشغيله, ثم يتوقف مباشرةً عن العمل.

ثم قمنا بإنشاء كلاس إسمه Main, و الذي سيستخدم الكلاس ExamTimer كمؤقت.
في الكلاس Main فعلنا الأشياء التالية:

  • أنشأنا كائن من الكلاس ExamTimer إسمه et.

  • أنشأنا كائن من الكلاس Thread إسمه t و ربطناه بالكائن et.

  • قمنا بتشغيل الكائن t بواسطة الدالة start().

  • قمنا بتعريف المتغيرات num1 و num2 لتخزين الأرقام العشوائية التي سيتم توليدها في البرنامج.

  • قمنا بتعريف المتغير userAnswer لتخزين العدد الذي سيدخله المستخدم في كل مرة.

  • قمنا بتعريف المتغيرات operationsCounter, correctAnswersCounter و wrongAnswersCounter كعدادات في البرنامج.
    operationsCounter: لتخزين عدد العمليات التي تظهر أمام المستخدم.
    correctAnswersCounter: لتخزين عدد إجابات المستخدم الصحيحة.
    wrongAnswersCounter: لتخزين عدد إجابات المستخدم الخاطئة.

  • قمنا بتعريف حلقة while تستمر في توليد أرقام العشوائية, إعداد عمليات جمع و إنتظار المستخدم لمعرفة الإجابة إلخ.. بالإعتماد على الدالة isAlive() التي تبقي الحلقة تعيد تنفيذ الأوامر الموجودة فيها طالما أن مدة الإنتظار المحددة للكائن t غير منتهية بعد.

ExamTimer.java
public class ExamTimer implements Runnable {
 
    @Override
    public void run() {
        // لثانية واحدة Thread بعدها سيتم إيقاف كائن الـ
        try {
            Thread.sleep(20000);
        }
        catch(Exception e) {
            System.out.println(e.getMessage());
        }
    }
 
}
		

Main.java
import java.util.Scanner;		// Scanner هنا قمنا باستدعاء الكلاس
 
public class Main {
 
    public static void main(String[] args) {
 
        // و الذي سنستخدمه لإدخال بيانات من المستخدم input إسمه Scanner هنا قمنا بإنشاء كائن من الكلاس
        Scanner input = new Scanner(System.in);
 
        // et إسمه ExamTimer هنا قمنا بإنشاء كائن من الكلاس
        ExamTimer et = new ExamTimer();
 
        // et و ربطناه بالكائن t إسمه Thread هنا قمنا بإنشاء كائن من الكلاس
        Thread t = new Thread(et);
 
        int num1;                           // سنستخدم هذا المتغير لتخزين أول رقم عشوائي يظهر في عملية الجمع
        int num2;                           // سنستخدم هذا المتغير لتخزين ثاني رقم عشوائي يظهر في عملية الجمع
        int userAnswer;                     // سنستخدم هذا المتغير لتخزين العدد الذي سيدخله المستخدم للإجابة على عمليأت الجمع
        int operationsCounter = 0;          // سنخزن عدد العمليات الحسابية التي ستظهر عند تشغيل البرنامج فيه
        int correctAnswersCounter = 0;      // سنخزن عدد الإجابات الصحيحة في هذا المتغير
        int wrongAnswersCounter = 0;        // سنخزن عدد الإجابات الخطئ من ي هذا المتغير
 
        // الأمر الذي سيجعله في حالة إنتظار مدة 20 ثانية فقط و بعدها سيتوقف كلياً t هنا قمنا بتشغيل الكائن
        t.start();
 
        System.out.println("---------- Quiz ---------");
 
        // طالما أن مدة العشرين ثانية لم تنقضي بعد سيستمر في تنفيذ الأوامر الموجودة في هذه الحلقة
        while(t.isAlive())
        {
            num1 = (int)(Math.random()*10);             // num1 سيتم تخزين رقم عشوائي بين 1 و 9 في المتغير
            num2 = (int)(Math.random()*10);             // num2 سيتم تخزين رقم عشوائي بين 1 و 9 في المتغير
 
            System.out.print(num1+" + "+num2+" = ");    // num2 و num1 هنا سيطلب من المستخدم معرفة ناتج جمع العددين
            userAnswer = input.nextInt();               // هنا سيتم إنتظار المستخدم لإدخال الجواب
 
            if(userAnswer == num1+num2)                 // إذا كانت إجابته صحيحة, سيتم إضافة عدد الإجابات الصحيحة واحداً
                correctAnswersCounter++;
 
            else                                        // إذا كانت إجابته خاطئة, سيتم إضافة عدد الإجابات الخطأ واحداً
                wrongAnswersCounter++;
 
            operationsCounter++;                        // في الأخير سيتم إضافة عدد عمليات الجمع واحداً
        }
 
        System.out.println("Time end..\n");
 
        // بعد إنتهاء مدة العشرين ثانية سيتم طباعة عدد عمليات الجمع, عدد الأجوبة الصحيحة و عدد الأجوبة الخاطئة
        System.out.println("--------- Result --------");
        System.out.println("Number of operations:      " +operationsCounter);
        System.out.println("Number of correct answers: " +correctAnswersCounter);
        System.out.println("Number of wrong answers:   " +wrongAnswersCounter);
 
    }
 
}
		

عند تشغيل البرنامج, سنحصل على نتيجة تشبه النتيجة التالية عند التشغيل.
الأرقام التي قمنا بتعليمها باللون الأصفر هي التي قمنا بإدخالها عند تجربة البرنامج.

---------- Quiz ---------
0 + 8 = 8
5 + 7 = 11
8 + 8 = 16
6 + 6 = 12
3 + 9 = 12
7 + 4 = 14
9 + 4 = 13
Time end..

--------- Result --------
Number of operations:      7
Number of correct answers: 5
Number of wrong answers:   2


المثال الثالث

في المثال التالي قمنا تشغيل أكثر من كائن Runnable في وقت واحد. الهدف هنا جعلك تدرك أن الأوامر لا تتنفذ فعلياً في وقت واحد لكنها تتنفذ بسرعة عالية جداً تجعل المستخدم يظن أنها تحدث في وقت واحد.

في البداية قمنا بإنشاء كلاس إسمه MyRunnable يطبق الإنترفيس Runnable.
بعدها فعلنا Override للدالة run() حتى نجعل أي كائن من الكلاس MyRunnable يطبع إسمه كل ثانية.

ثم قمنا بإنشاء كلاس إسمه Main, و الذي قمنا فيه بإنشاء ثلاث كائنات من الكلاس MyRunnable, و ربطهم بثلاث كائنات من الكلاس Thread و تشغيلهم مع بعض.

MyRunnable.java
public class MyRunnable implements Runnable {
 
    private String name;
 
    // سنقوم بإعطائه إسم MyRunnable عند إنشاء كائن من الكلاس
    public MyRunnable(String name) {
        this.name = name;
    }
 
 
    @Override
    public void run() {
 
        // true لا ترجع isInterrupted() طالما أن الدالة
        while (!Thread.currentThread().isInterrupted())
        {
            // الذي قام بإستدعائها Thread سيتم طباعة إسم كائن الـ
            System.out.println(name);
 
            // بعدها سيتم إيقافه مدة ثانية واحدة
            try {
                Thread.sleep(1000);
            }
            catch(Exception e) {
                System.out.println(e.getMessage());
            }
        }
 
    }
 
}
		

Main.java
public class Main {
 
    public static void main(String[] args) {
 
        // مع إعطاء كل واحد منهم إسم مختلف ( mr1 - mr2 - mr3 ) إسمهم MyRunnable هنا قمنا بإنشاء ثلاث كائنات من الكلاس
        MyRunnable mr1 = new MyRunnable("Runnable 1");
        MyRunnable mr2 = new MyRunnable("Runnable 2");
        MyRunnable mr3 = new MyRunnable("Runnable 3");
 
        // ( mr1 - mr2 - mr3 ) و ربطناههم بالكائنات ( t1 - t2 - t3 ) إسمهم Thread هنا قمنا بإنشاء ثلاث كائنات من الكلاس
        Thread t1 = new Thread(mr1);
        Thread t2 = new Thread(mr2);
        Thread t3 = new Thread(mr3);
 
        // كل ثانية ( t1 - t2 - t3 ) هنا قمنا بتشغيل جميع الكائنات, و بالتالي سيتم طباعة أسماء الكائنات
        t1.start();
        t2.start();
        t3.start();
 
    }

}
		

عند تشغيل البرنامج, نلاحظ أنه يطبع ثلاثة أسطر كل ثانية .

Runnable 1
Runnable 3
Runnable 2

Runnable 3
Runnable 2
Runnable 1

Runnable 2
Runnable 3
Runnable 1

Runnable 2
Runnable 3
Runnable 1

Runnable 1
Runnable 2
Runnable 3
... 
		


المثال الرابع

في المثال التالي قمنا بإنشاء برنامج يظهر أمام المستخدم لائحة فيها أربع خيارات: تشغيل صوت التنبيه, إيقافه, إعادته للعمل, إيقافه نهائياً.
ميزة هذا البرنامج أنه يمكن تشغيل الإنذار أو إيقافه في نفس الوقت الذي يطلب فيه من المستخدم إدخال رقم الخيار.

في البداية قمنا بإنشاء برنامج يظهر أمام المستخدم لائحة فيها أربع خيارات: تشغيل إنذار, إيقافه, إعادته للعمل, إيقافه نهائياً.
ميزة هذا البرنامج يمكنه تشغيل الإنذار في نفس الوقت الذي يطلب فيه من المستخدم إدخال رقم الخيار.

في البداية قمنا بإنشاء كلاس إسمه Alert يطبق الإنترفيس Runnable.

بعدها فعلنا Override للدالة run() حتى نجعل أي كائن من الكلاس Alert يصدر صوت تنبيه عند تشغيله.

ثم قمنا بإنشاء كلاس إسمه Main لتجربة الكلاس Alert.
عند تشغيل البرنامج ظهر لك أربع خيارات: تشغيل صوت التنبيه, إيقافه, إعادته للعمل, إيقافه نهائياً.
ستلاحظ أن البرنامج قادر على تشغيل صوت التنبيه في نفس الوقت الذي يطلب فيه من المستخدم إدخال رقم الخيار.

إنتبه: إرفع صوت الحاسوب إلا أعلا مستوى حتى تسمع صوت التنبيه عند تشغيله.

Alert.java
import java.awt.Toolkit;		// لأننا سنستخدمه للحصول على صوت التنبيه Toolkit هنا قمنا باستدعاء الكلاس
 
public class Alert implements Runnable {
 
    @Override
    public void run() {
 
        // true ترجع isInterrupted() طالما أن الدالة
        while (!Thread.isInterrupted())
        {
            // سيتم إصدار صوت المنبه كل ثانية
            try {
                Toolkit.getDefaultToolkit().beep();
                Thread.sleep(1000);
            }
            catch(Exception e) {
                System.out.println(e.getMessage());
            }
        }
 
    }
 
}
		

Main.java
import java.util.Scanner;                   // Scanner هنا قمنا باستدعاء الكلاس
 
public class Main {
 
    public static void main(String[] args) {
 
        // و الذي سنستخدمه لإدخال بيانات من المستخدم input إسمه Scanner هنا قمنا بإنشاء كائن من الكلاس
        Scanner input = new Scanner(System.in);
 
        // سنستخدم هذا المتغير لتخزين العدد الذي سيدخله المستخدم في كل مرة
        int userInput;
 
        // alert إسمه Alert هنا قمنا بإنشاء كائن من الكلاس
        Alert alert = new Alert();
 
        // alert و ربطناه بالكائن alertThread إسمه Thread هنا قمنا بإنشاء كائن من الكلاس
        Thread alertThread = new Thread(alert);
 
        // حتى نضمن أن لا يتم إستدعاءها مرتين start() سنستخدم هذا المتغير كشرط أساسي في الدالة
        boolean isStarted = false;
 
 
        // هنا قمنا بطباعة شكل لائحة من الخيارات أمام المستخدم
        System.out.println("----- Alert Menu ------\n"
                          +"| Enter (1) to Start  |\n"
                          +"| Enter (2) to Pause  |\n"
                          +"| Enter (3) to Resume |\n"
                          +"| Enter (4) to Stop   |\n"
                          +"-----------------------");
 
        // interrupt() بواسطة الدالة alertThread() هذه الحلقة تستمر في تنفيذ ما في داخلها طالما أنه لم يتم إيقاف الكائن
        while(!alertThread.isInterrupted())
        {
            // هنا سيتم إنتظار المستخدم ليدخل رقم الخيار الذي يريد
            System.out.print("User input >> ");
            userInput = input.nextInt();
 
            // هنا سيتم فحص القيمة التي أدخلها المستخدم
            switch(userInput)
            {
                // في حال قام بإدخال الرقم 1 سيتم تشغيل التنبيه
                case 1:
                    // أكثر من مرة واحدة start() الشرط التالي يضمن أن لا يقوم المستخدم باستدعاء الدالة
                    if (isStarted == false) {
                        alertThread.start();
                        isStarted = true;
                    }
                    // بدلاً منها resume() بعد إستدعائها للمرة للأولى, سيتم إستدعاء الدالة start() هنا قلنا أن أي إستدعاء للدالة
                    else {
                        alertThread.resume();
                    }
                    break;
 
                // في حال قام بإدخال الرقم 2 سيتم إيقاف التنبيه مع إمكانية تشغيله من جديد
                case 2:
                    alertThread.suspend();
                    break;
 
                // في حال قام بإدخال الرقم 3 سيتم إعادة التنبيه للعمل من جديد
                case 3:
                    alertThread.resume();
                    break;
 
                // في حال قام بإدخال الرقم 4 سيتم إيقاف التنبيه مع عدم إمكانية تشغيله من جديد
                case 4:
                    alertThread.interrupt();
                    alertThread.stop();
                    break;
 
                // في حال قام بإدخال رقم أكبر من 4 أو أصغر من واحد سيتم إظهار خطأ له
                default:
                    System.out.println("Option <"+userInput+"> Not Found!");
                    break;
            }
 
        }
 
    }

}
		

قم بتشغيل البرنامج و أدخل نفس القيم التي قمنا بتعليمها باللون الأصفر لتلاحظ كيف يعمل.
لا تنسى رفع صوت حاسوبك إلى أعلا مستوى حتى تسمع صوت التنبيه و هو يعمل.

----- alertThread.Menu ------
| Enter (1) to Start  |
| Enter (2) to Pause  |
| Enter (3) to Resume |
| Enter (4) to Stop   |
-----------------------
User input >> 1
User input >> 2
User input >> 3
User input >> 2
User input >> 3
User input >> 4

أهمية الإنترفيس Runnable في التطبيقات الفعلية

أهم نقطة في تطبيق الإنترفيس Runnable هي أنه يجعلك قادراً على بناء كلاس يعمل كـ Thread و بنفس الوقت يرث من كلاس آخر.

مثال

MyRunnable.java
public class MyRunnable extends AnOtherClass implements Runnable {

    @Override
    public void run(){
        // Thread من كائن من الكلاس start() هنا تضع الأوامر التي تريد تنفيذها عند إستدعاء الدالة
    }

}
		

هذا الأسلوب مهم جداً و قد تحتاجه عند بناء تطبيق فيه واجهة مستخدم (GUI) و يعمل بشكل متوازي مع باقي الأوامر الموجودة في البرنامج.
لأنه عند بناء تطبيق فيه GUI عليك أن تفعل extends من كلاس محدد مثل الكلاس JFrame, و تفعل implements للإنترفيس Runnable.

كمثال بسيط, هذا الأسلوب يجعلك قادراً على بناء تطبيق فيه واجهة مستخدم و يعرض لك الوقت و التاريخ.
لا تقلق ستتعلم هذه الأمور في دروس لاحقة.