تعدد المهام في جافا | Java Multithreading

 تعدد المهام في جافا | Java Multithreading

عندما تستخدم هاتفك أو حاسوبك, ترى أنه يمكنك تشغيل عدة برامج مع بعض في وقت واحد, كل برنامج شغال في الذاكرة يعتبر Process, فمثلاً إذا قمت بتشغيل خمسة برامج مع بعض فهذا يعني أن نظام التشغيل ينظم عمل خمسة Processes مع بعض. آلية تشغيل عدة برامج مع بعض تسمى Multiprocessing.

من جهة اخرى, في البرنامج الواحد يمكنك تنفيذ عدة أوامر مع بعض و جعل المستخدم يشعر كأنها تتنفذ في وقت واحد, فمثلاً في حال كنت تلعب لعبة مثل لعبة كرة القدم, تجد أنه هناك عدة أشياء تحدث في وقت واحد, فمثلاُ عند تشغيل اللعبة تسمع عدة أصوات ( مثل أغنية حماسية, صوت المعلق, صوت المشجعين, صوت صفارة الحكم في حال وقع خطأ إلخ.. ), بالإضافة إلى أنه يمكنك تحريك اللاعب و مشاهدة توقيت المبارة و الكثير من التفاصيل الأخرى التي تحدث كلها في نفس الوقت لتصنع لك لعبة رائعة. هذه الآلية تسمى Multithreading, لأن كل جزء شغال في البرنامج يكون عبارة عن مجموعة أوامر موضوعة بداخل Thread خاص.

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


معلومة تقنية

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

الفرق بين الـ Process و الـ Thread

Process تعني برنامج شغال حالياً, و قد قام نظام التشغيل بحجز مساحة خاصة له في الذاكرة.

Thread عبارة عن مجموعة أوامر يتم تنفيذها أثناء تنفيذ أوامر أخرى في نفس البرنامج. يمكنك تشغيل أكثر من Thread في نفس الوقت في البرنامج, و يمكن أيضاً مشاركة المعلومات بينهم. مع ملاحظة أنه يتم إنشاء جميع الـ Threads من ضمن المساحة المحجوزة للـ Process في الذاكرة.

الفرق بين ال process و ال thread

فوائد الـ Multithreading

  • جعل المستخدم قادر على تنفيذ عدة عمليات مع بعض في نفس الوقت.

  • جعل تصميم التطبيقات أجمل و إضافة مؤثرات فيه.

  • كل Thread تقوم بتشغيله, يعمل بشكل منعزل عن باقي الأوامر الموجودة في البرنامج, و بالتالي فإنه في حال وقوع أي خطأ في الـ Thread فإنه لن يؤثر على باقي الأوامر الموجود في البرنامج, كما أنه لا يؤثر على أي Thread آخر شغال في البرنامج.

دورة حياة الـ Thread ( Thread Life Cycle )

الـ Thread لا يعمل مباشرةً عند إنشاءه, بل يمر بعدة مراحل تباعاً حتى يعمل و ينفذ الأوامر الموضوعة بداخله, و هو ينتقل من مرحلة إلى أخرى من خلال دوال معينة كما في الصورة التالية.

المراحل التي يمر بها ال thread


  1. المرحلة New: هي المرحلة التي يتم فيها إنشاء كائن الـ Thread.

  2. المرحلة Runnable: تبدأ هذه المرحلة بعد إستدعاء الدالة run(), و هي تعني أن كائن الـ Thread جاهزاً لينفذ الأوامر الموضوعة فيه.

  3. المرحلة Running: تبدأ هذه المرحلة بعد إستدعاء الدالة start(), و هي تعني أن كائن الـ Thread يبدأ بتنفيذ الأوامر الموضوعة فيه.

  4. المرحلة Waiting: تبدأ هذه المرحلة بعد إستدعاء الدالة wait() أو sleep(), و هي تعني أن كائن الـ Thread متوقف لمدة معينة.

  5. المرحلة Dead: يقال لها Terminated أيضاً, و هي تعني إيقاف كائن الـ Thread كلياُ عن العمل و مسحه من الذاكرة, تبدأ هذه المرحلة بعد إستدعاء الدالة interrupt().


ملاحظة

الهدف هنا فقط توضيح آلية عمل الـ Thread حتى لا تجد صعوبة في فهمها.
ركز قليلا على الدوال التي تنقل الـ Thread من مرحلة لأخرى, لكن لا تحفظ أسماء المراحل.

مفهوم الـ Main Thread

عندما تقوم بتشغيل أي برنامج في جافا, فإن جافا تعامله كـ Thread, لأن كل برنامج تقوم بإنشائه يمر بالمراحل التي ذكرناها.

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

  1. سيتم فحص كود البرنامج للتأكد من عدم وجود أي خطأ.

  2. بعدها سيتم البحث عن الدالة main() التي تعتبر نقطة البداية في البرنامج.

  3. بعدها سيتم تنفيذ الأوامر الموضوعة في الدالة main() بالترتيب الموضوعين فيه. و بالتالي سيطلب البرنامج من المستخدم إدخال عددين.

  4. بعدها سيقوم البرنامج بإنتظار المستخدم لإدخال العددين.

  5. بعد إدخالهما سيقوم بجمع العددين و طباعة الناتج. و عندها ينتهي البرنامج و يتم مسحه من الذاكرة بشكل تلقائي.



لتتأكد أكثر, قمنا ببناء برنامج فيه خطأ متعمد. لاحظ أنه عند تشغيل البرنامج سيخبرك أنه توجد مشكلة في الـ thread الذي إسمه main.

مثال

Main.java
public class Main {
 
    public static void main(String[] args) {
 
        // و وضعنا فيه نص بدل أن نضع فيه رقم int كـ x هنا يوجد خطأ في الكود و هو أننا قمنا بتعريف المتغير
        int x = "i am a string!";
 
    }

}
		

سنحصل على نتيجة تشبه النتيجة التالية عند التشغيل.

Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - incompatible types
&nbsp required: int
&nbsp found: &nbsp&nbsp java.lang.String

لاحظ أنه أخبرك أن المشكلة حدثت في الـ thread "main".

الكلاسات المصممة للتعامل مع الـ Thread

الجدول التالي يحتوي على كلاسات مصممة خصيصاً لإنشاء Thread و التحكم بطريقة عمله.
ملاحظة: جميع هذه الكلاسات مرتبطة مع بعضها و موضوعة بترتيب منظم, لذلك عليك قراءتهم واحداً تلو الآخر لتفهمهم جيداً.


الكلاس مع تعريفه
public class Thread يمكنك إنشاء الـ Thread من خلال وراثة الكلاس Thread.
تابع القراءة »
public interface Runnable المشكلة الوحيدة التي تواجهها عند إنشاء الـ Thread من خلال وراثة الكلاس Thread هي أن الكلاس يصبح غير قادر على أن يرث من كلاس آخر, لأن جافا لا تدعم تعدد الوراثة, و بالتالي لا يستطيع الكلاس أن يفعل extends لأكثر من كلاس.
لحل مشكلة تعدد الوراثة, يمكنك جعل الكلاس يفعل implements للإنترفيس Runnable و تنشئ كائن من الكلاس Thread بداخله.
تابع القراءة »
public class ThreadGroup يمكنك أيضاً استخدام الكلاس ThreadGroup لإنشاء أكثر من Thread و وضعهم في مجموعة واحدة, مما يجعلك قادراً على التعامل معهم جميعاً دفعة واحدة.
تابع القراءة »
public class Executors + public interface ExecutorService ملاحظة: هناك العديد من الكلاسات و الإنترفيسات الأخرى التي تستخدم لإنشاء Threads و التعامل معهم بطرق مختلفة.
هنا وضعنا مثال طبقنا فيه مبدأ يسمى Thread Pool, حيث أننا إستخدمنا فيه الكلاس Executors و الإنترفيس ExecutorService لإنشاء خمسة Thread من نفس الـ Thread و تشغيلهم دفعة واحدة, بدل إنشاء كل Thread على حدة و تشغيله.
شاهد المثال »
_______&&&______;&&&___________;&&&&________; _______&&&______;&&&___________;&&&&________;

 الكلاس Thread في جافا

مقدمة

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

كل كائن نوعه Thread تقوم بإنشائه, يكون الهدف منه تنفيذ مجموعة أوامر أثناء تنفيذ أوامر أخرى.

كل كائن Thread تقوم بإنشاءه, يتم إعطاءه رقم تعرفة ( id ) و إسم ( name ) خاص فيه, بالتالي يمكن الوصول لكائن الـ Thread من خلال رقم التعرفة أو الإسم الذي أعطي له.

بالإضافة إلى ذلك, يملك كل كائن Thread رقم بين 1 و 10 يحدد أولية التنفيذ ( Priority ).
كائن الـ Thread الذي يملك أعلا Priority يتم تنفيذ أوامره في الأول. لذلك, تلاعب بهذا الرقم فقط في حال كان عندك كائن Thread أهم من غيره.
تلقائياً, أي كائن Thread تقوم بإنشائه, يتم إعطائه Priority تساوي 5 حتى يتم تنفيذ جميع أوامر كائنات الـ Thread بشكل متوازي و عادل.


بناؤه

                  public class Thread
                  extends Object
                  implements Runnable
                

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



خطوات بناء Thread بواسطة الكلاس Thread

  1. نفعل extends للكلاس Thread.

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


مثال

MyThread.java
                    public class MyThread extends Thread {

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

                    } 
                  

خطوات إنشاء الـ Thread و تشغيله

  1. ننشئ كائن من الكلاس الذي يرث من الكلاس Thread.

  2. نستدعي الدالة start() لتشغيل كائن الـ Thread.

مثال

Main.java
                    public class Main {

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

                    MyThread t = new MyThread();
                    t.start();

                    }

                    } 
                  

إنتبه

في حال قمت بتشغل كائن الـ Thread بواسطة الدالة run() بدل الدالة start(), عندها ستعامل الدالة run() كدالة عادية, أي سيتم إيقاف باقي الأوامر الموجودة في الدالة main() حتى يتم تنفيذ الأوامر الموجودة بداخل الدالة run() ثم العودة للدالة main() لمتابعة تنفيذ باقي الأوامر الموجودة فيها.

الحالات التي يمر بها الـ Thread (Thread States)

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

في الجدول التالي ذكرنا الحالات التي يمر بها الـ Thread.

الحالة معناها
NEW
هذه الحالة تعني أن كائن الـ Thread قد تم إنشاءه في الذاكرة. RUNNABLE هذه الحالة تعني أن كائن الـ Thread أصبح جاهزاً و يمكن تنفيذ أوامره. TIMED_WAITING هذه الحالة تعني أن كائن الـ Thread قد تم إيقافه عن التنفيذ لمدة معينة. BLOCKED هذه الحالة تعني أن كائن الـ Thread موقوف لحين تنفيذ أوامر Thread آخر. WAITING هذه الحالة تعني أن كائن الـ Thread تم إيقافه عن التنفيذ لبعض الوقت ريثما ينتهي تنفيذ أوامر Thread آخر. TERMINATED هذه الحالة تعني أن كائن الـ Thread تم إيقافه نهائياً, و تم مسحه من الذاكرة.

ملاحظة: بعد أن تصبح حالة كائن الـ Thread تساوي TERMINATED يمكن إعطاء رقم التعرفة و إسم الكائن السابق لكائن Thread آخر.

كونستركتورات الكلاس Thread

في الجدول التالي ذكرنا جميع كونستركتورات الكلاس Thread.

الكونستركتور مع تعريفه
public Thread() ينشئ كائن نوعه Thread ليس له إسم محدد.
public Thread(String name) ينشئ كائن نوعه Thread مع تحديد إسمه.
public Thread(Runnable target) ينشئ كائن نوعه Thread من كائن نوعه Runnable.
public Thread(Runnable target, String name) ينشئ كائن نوعه Thread من كائن نوعه Runnable مع تحديد إسمه.
public Thread(ThreadGroup group, String name) ينشئ كائن نوعه Thread و يضعه ضمن مجموعة محددة, مع تحديد إسمه.

باراميترات
  • group عبارة عن كائن نوعه ThreadGroup يمثل مجموعة من الـ Threads.

  • name عبارة عن إسم يتم إعطاءه لكائن الـ Thread الذي سيتم إنشاءه و وضعه ضمن المجموعة المذكورة.


يرمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية إنشاء كائن Thread جديد بداخل المجموعة المشار إليها.
public Thread(ThreadGroup group, Runnable target) ينشئ كائن نوعه Thread من كائن نوعه Runnable و يضعه ضمن مجموعة محددة.

باراميترات
  • group عبارة عن كائن نوعه ThreadGroup يمثل مجموعة من الـ Threads.

  • target عبارة عن كائن نوعه Runnable يمثل كائن الـ Thread الذي سيتم إنشاءه و وضعه ضمن المجموعة المذكورة.


يرمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية إنشاء كائن Thread جديد بداخل المجموعة المشار إليها.
public Thread(ThreadGroup group, Runnable target, String name) ينشئ كائن نوعه Thread من كائن نوعه Runnable و يضعه ضمن مجموعة محددة, مع تحديد إسمه.

باراميترات
  • group عبارة عن كائن نوعه ThreadGroup يمثل مجموعة من الـ Threads.

  • target عبارة عن كائن نوعه Runnable يمثل كائن الـ Thread الذي سيتم إنشاءه و وضعه ضمن المجموعة المذكورة.

  • name عبارة عن إسم يتم إعطاءه لكائن الـ Thread الذي سيتم إنشاءه و وضعه ضمن المجموعة المذكورة.


يرمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية إنشاء كائن Thread جديد بداخل المجموعة المشار إليها.
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) ينشئ كائن نوعه Thread من كائن نوعه Runnable و يضعه ضمن مجموعة محددة, مع تحديد إسمه, و تحديد المساحة القصوى التي يمكن أن تحجز له في الذاكرة.

باراميترات
  • group عبارة عن كائن نوعه ThreadGroup يمثل مجموعة من الـ Threads.

  • target عبارة عن كائن نوعه Runnable يمثل كائن الـ Thread الذي سيتم إنشاءه و وضعه ضمن المجموعة المذكورة.

  • name عبارة عن إسم يتم إعطاءه لكائن الـ Thread الذي سيتم إنشاءه و وضعه ضمن المجموعة المذكورة.

  • stackSize عبارة عن المساحة القصوى التي يمكن أن يحتلها كائن الـ Thread في الذاكرة.


يرمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية إنشاء كائن Thread جديد بداخل المجموعة المشار إليها.

دوال الكلاس Thread

الجدول التالي يحتوي على دوال الكلاس Thread.

الدالة مع تعريفها
public void run() تستخدم لتجهيز الأوامر التي ستنفذ عند تشغيل الـ Thread.
الكلاس الذي يرث من الكلاس Thread يجب أن يفعل لها Override, و يضع بداخلها الأوامر التي يريدها أن تتنفذ عند تشغيل الـ Thread.
ملاحظة: لتشغيل الـ Thread عليك إستدعاء الدالة start() و التي بدورها تقوم باستدعاء الدالة run(), أي تنفذ الأوامر الموجودة فيها.
public void start() تستخدم لتشغيل كائن الـ Thread, أي لتنفيذ الأوامر التي تم وضعها في الدالة run().
ترمي الإستثناء IllegalThreadStateException في حال كان كائن الـ Thread شغالا قبل استدعائها.
public static void sleep(long milliseconds) توقف تنفيذ أوامر كائن الـ Thread الذي قام باستدعائها لمدة محددة. بعد إنتهاء هذه المدة يعود للعمل من جديد.
milliseconds هو المدة التي سيتوقف فيها كائن الـ Thread بالـ Milli Seconds.
فمثلاً إذا وضعت 1000 مكان هذا الباراميتر, سؤدي ذلك إلى إيقاف كائن الـ Thread عن التنفيذ لثانية واحدة.
ترمي الإستثناء IllegalThreadStateException في حال تم إعطاءها قيمة أصغر من 0.
ترمي الإستثناء InterruptedException في حال تم إيقاف كائن الـ Thread في فترة إنتظاره.
public static void suspend() توقف تنفيذ أوامر كائن الـ Thread الذي قام باستدعائها لمدة غير محددة, بحيث لا يمكنه العودة للعمل من جديد إلا إذا قام باستدعاء الدالة resume() بعدها.
ترمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية التعديل على كائن الـ Thread المتوقف عن العمل.
public static void resume() تكمل تنفيذ أوامر كائن الـ Thread بعد أن كان قد توقف عن تنفيذها بسبب الدالة suspend().
ترمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية التعديل على كائن الـ Thread المتوقف عن العمل.
public static void stop() توقف كائن الـ Thread الذي قام باستدعائها, بحيث لا يمكنه العودة للعمل من جديد.
ترمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية التعديل على كائن الـ Thread المتوقف عن العمل.
public final void join() تجعل البرنامج لا يتابع تنفيذ الأوامر الموجودة في الدالة main() حتى يتم تنفيذ جميع الأوامر الموجودة في كائن الـ Thread الذي قام باستدعائها, أي حتى تصبح حالته تساوي TERMINATED.
ملاحظة: لا يهمها إذا كان يوجد أكثر من كائن Thread آخر شغال في نفس الوقت. بمعنى أنه إذا كان هناك كائن Thread آخر شغال فإنه سيستمر في العمل بشكل طبيعي, لكن باقي الأوامر الموجودة في الدالة main() لن يتم تنفيذهم حتى تنفيذ جميع أوامر كائن الـ Thread الذي قام باستدعائها.

ترمي الإستثناء InterruptedException في حال تم إيقاف كائن الـ Thread من قبل أي كائن Thread آخر.
public void interrupt() ترسل إشارة إلى الـ JVM لمقاطعة عمل كائن الـ Thread في حال كان شغالاً.
في حال كان كائن الـ Thread متوقفاً لمدة بسبب إحدى الدوال مثل join() و wait() و sleep(), فإنها لا تؤثر عليه أبداً.
في حال كان كائن الـ Thread غير شغال بعد لا تؤثر عليه أيضاً.

ترمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية التعديل على كائن الـ Thread المتوقف عن العمل.

معلومة: قد تحتاج هذه الدالة في حال كنت تريد إيقاف كائن الـ Thread ريثما تنتهي من تنفيذ أوامر أخرى أكثر أهمية, في حال نجحت هذه الدالة تكون مدة إيقاف كائن الـ Thread عن العمل غير محددة.
public boolean isInterrupted() تستخدم لمعرفة إذا كان كائن الـ Thread قد توقف بسبب استدعاء الدالة interrupt() أم لا.
ترجع true في حال تم استدعاء الدالة interrupt() قبلها و قد تمكنت هذه الأخيرة من مقاطعة كائن الـ Thread أثناء تنفيذ الأوامر.
غير ذلك ترجع false.
public long getId() ترجع رقم الـ ID الذي تم إعطاءه لكائن الـ Thread.
public Thread.State getState() ترجع كائن نوعه Thread.State يمثل حالة كائن الـ Thread الذي قام باستدعائها.
public final boolean isAlive() ترجع true في حال لم يكن كائن الـ Thread الذي قام باستدعائها في الحالة TERMINATED.
غير ذلك ترجع false.
public String toString() ترجع نص يمثل معلومات كائن الـ Thread الذي قام باستدعائها. المعلومات عبارة عن ( Group - Priority - Name ).
  • Name: هو إسم كائن الـ Thread.

  • Priority: هو أولية التفيذ المعطاة لكائن الـ Thread.

  • Group: هو إسم المجموعة التي ينتمي لها كائن الـ Thread.

public final void setName(String name) تستخدم لإعطاء إسم لكائن الـ Thread.
ترمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية تغييرها.
public final String getName() ترجع إسم كائن الـ Thread.
public final int setPriority(int newPriority) تستخدم لتغيير قيمة الـ Priority المعطاة لكائن الـ Thread.
  • الـ Priority التي تعطى إفتراضياً لأي Thread جديد هي 5, و هي تساوي قيمة الثابت NORM_PRIORITY.

  • أعلا Priority يمكن إعطاءها للـ Thread هي 10, و هي تساوي قيمة الثابت MAX_PRIORITY.

  • أدنى Priority يمكن إعطاءها للـ Thread هي 1, و هي تساوي قيمة الثابت MIN_PRIORITY.


ترمي الإستثناء IllegalArgumentException في حال كانت القيمة التي قمت بتمريرها أصغر من 1 أو أكبر من 10.
و ترمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية تغييرها.
public final int getPriority() ترجع رقم بين 1 و 10 عبارة عن قيمة الـ Priority التي يملكها كائن الـ Thread الذي قام باستدعائها.
public final void setDaemon(boolean on) تستخدم لتحويل كائن الـ Thread الذي قام باستدعائها إلى Daemon Thread.
في حال قمت بتمرير القيمة true لها كـ Argument, سيتم معاملة كائن الـ Thread كــ Daemon Thread
غير ذلك سيتم إعتباره Thread عادي.

ترمي الإستثناء IllegalArgumentException في حال كان كائن الـ Thread شغالا أثناء استدعاءها.
و ترمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية تغييرها.
public final boolean isDaemon() تستخدم لمعرفة إذا كان كائن الـ Thread الذي قام باستدعائها هو Daemon Thread أم لا.
ترجع true إذا كان كذلك, غير ذلك ترجع false.
public static int activeCount() تستخدم لمعرفة عدد الـ threads الذين يعملون في البرنامج لحظة استدعائها.
تستخدم فقط في حالة الـ Debugging.
ملاحظة: في حال لم يكن هناك أي Thread شغال أو معرف في البرنامج, فإنها أيضاً ترجع 1 و الذي هو عبارة عن البرنامج نفسه.
public static Thread currentThread() ترجع كائن نوعه Thread يشير إلى كائن الـ Thread الذي قام باستدعائها.
public final ThreadGroup getThreadGroup() ترجع كائن نوعه ThreadGroup يمثل المجموعة التي ينتمي إليها كائن الـ Thread الذي قام باستدعائها.
ترجع true في حال كانت حالة كائن الـ Thread تساوي TERMINATED.

أمثلة شاملة 

المثال الأول

المثال هذا عبارة عن برنامج يعرض الوقت الحالي للجهاز.

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

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

RealTime.java
                    import java.util.Date;    // Date هنا قمنا باستدعاء الكلاس

                    public class RealTime extends Thread {

                    @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) {

                    // t إسمه RealTime هنا قمنا بإنشاء كائن من الكلاس
                    RealTime t = new RealTime();

                    // لعرض الوقت 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 يرث من الكلاس Thread.
بعدها فعلنا Override للدالة run() حتى نجعل أي كائن من الكلاس ExamTimer ينتظر مدة 20 ثانية بعد تشغيله, ثم يتوقف مباشرةً عن العمل.

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

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

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

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

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

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

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

ExamTimer.java
                    public class ExamTimer extends Thread {

                    @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();

                    int num1;                           // سنستخدم هذا المتغير لتخزين أول رقم عشوائي يظهر في عملية الجمع
                    int num2;                           // سنستخدم هذا المتغير لتخزين ثاني رقم عشوائي يظهر في عملية الجمع
                    int userAnswer;                     // سنستخدم هذا المتغير لتخزين العدد الذي سيدخله المستخدم للإجابة على عمليأت الجمع
                    int operationsCounter = 0;          // سنخزن عدد العمليات الحسابية التي ستظهر عند تشغيل البرنامج فيه
                    int correctAnswersCounter = 0;      // سنخزن عدد الإجابات الصحيحة في هذا المتغير
                    int wrongAnswersCounter = 0;        // سنخزن عدد الإجابات الخطئ من ي هذا المتغير

                    // الأمر الذي سيجعله في حالة إنتظار مدة 20 ثانية فقط و بعدها سيتوقف كلياً et هنا قمنا بتشغيل الكائن
                    et.start();

                    System.out.println("---------- Quiz ---------");

                    // طالما أن مدة العشرين ثانية لم تنقضي بعد سيستمر في تنفيذ الأوامر الموجودة في هذه الحلقة
                    while(et.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: &nbsp &nbsp&nbsp 7
Number of correct answers: 5
Number of wrong answers: &nbsp 2


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

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

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

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

MyThread.java
                    public class MyThread extends Thread {

                    // Thread كإسم لكائن الـ name و تمرير قيمة المتغير Thread سيتم مناداة الكونستركتور الذي ورثه من الكلاس MyThread عند إنشاء كائن من الكلاس
                    public MyThread(String name) {
                    super(name);
                    }


                    @Override
                    public void run() {

                    // true ترجع isInterrupted() طالما أن الدالة
                    while (!this.isInterrupted())
                    {
                    // الذي قام بإستدعائها Thread سيتم طباعة إسم كائن الـ
                    System.out.println(this.getName());

                    // بعدها سيتم إيقافه مدة ثانية واحدة
                    try {
                    Thread.sleep(1000);
                    }
                    catch(Exception e) {
                    System.out.println(e.getMessage());
                    }
                    }

                    }

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    // مع إعطاء كل واحد منهم إسم مختلف ( t1 - t2 - t3 ) إسمهم MyThread هنا قمنا بإنشاء ثلاث كائنات من الكلاس
                    MyThread t1 = new MyThread("Thread 1");
                    MyThread t2 = new MyThread("Thread 2");
                    MyThread t3 = new MyThread("Thread 3");

                    // كل ثانية ( t1 - t2 - t3 ) هنا قمنا بتشغيل جميع الكائنات, و بالتالي سيتم طباعة أسماء الكائنات
                    t1.start();
                    t2.start();
                    t3.start();

                    }

                    }
                  

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

                    Thread 1
                    Thread 3
                    Thread 2

                    Thread 3
                    Thread 2
                    Thread 1

                    Thread 2
                    Thread 3
                    Thread 1

                    Thread 2
                    Thread 3
                    Thread 1

                    Thread 3
                    Thread 2
                    Thread 1
                    ... 
                  


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

المثال التالي هو نفسه المثال السابق لكننا قمنا باستخدام الدالة run() بدلاً من الدالة start() حتى تعرف الفرق الحقيقي بينهما و ضرورة تشغيل كائن الـ Thread بواسطة الدالة start().

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

ثم قمنا بإنشاء كلاس إسمه Main, و الذي قمنا فيه بإنشاء ثلاث كائنات من الكلاس MyThread و تشغيلهم بواسطة الدالة run().

لاحظ كيف سيبقى البرنامج ينفذ أوامر أول كائن من الكلاس MyThread لأن الدالة run() تجعل البرنامج لا ينفذ أوامر أكثر من كائن Thread في وقت واحد.

MyThread.java
                    public class MyThread extends Thread {

                    // Thread كإسم لكائن الـ name و تمرير قيمة المتغير Thread سيتم مناداة الكونستركتور الذي ورثه من الكلاس MyThread عند إنشاء كائن من الكلاس
                    public MyThread(String name) {
                    super(name);
                    }


                    @Override
                    public void run() {

                    // true ترجع isInterrupted() طالما أن الدالة
                    while (!this.isInterrupted())
                    {
                    // الذي قام بإستدعائها Thread سيتم طباعة إسم كائن الـ
                    System.out.println(this.getName());

                    // بعدها سيتم إيقافه مدة ثانية واحدة
                    try {
                    Thread.sleep(1000);
                    }
                    catch(Exception e) {
                    System.out.println(e.getMessage());
                    }
                    }

                    }

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    // مع إعطاء كل واحد منهم إسم مختلف ( t1 - t2 - t3 ) إسمهم MyThread هنا قمنا بإنشاء ثلاث كائنات من الكلاس
                    MyThread t1 = new MyThread("Thread 1");
                    MyThread t2 = new MyThread("Thread 2");
                    MyThread t3 = new MyThread("Thread 3");

                    // مع بعض ( t1 - t2 - t3 ) هنا حاولنا تشغيل الكائنات
                    t1.run();       // آخر يعمل غيره Thread لأنه لا يوجد Thread سيتم تشغل هذا الـ
                    t2.run();       // آخر يعمل قبله Thread لأنه يوجد Thread لن يتم تشغيل هذا الـ
                    t3.run();       // آخر يعمل قبله Thread لأنه يوجد Thread لن يتم تشغيل هذا الـ

                    }

                    }
                  

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

                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    Thread 1
                    ... 
                  


المثال الخامس

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

في البداية قمنا بإنشاء كلاس إسمه Alert يرث من الكلاس Thread.
بعدها فعلنا Override للدالة start() لأننا لا نريد جعل البرنامج يعلق في حال قام المستخدم باستدعاء الدالة start() مرة ثانية.
فعلياً قلنا أن أي إستدعاء جديد للدالة start() بعد إستدعائها في المرة الأولى سيتم إستدعاء الدالة resume() بدلاً منها.

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

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

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

Alert.java
                    import java.awt.Toolkit;    // لأننا سنستخدمه للحصول على صوت التنبيه Toolkit هنا قمنا باستدعاء الكلاس

                    public class Alert extends Thread {

                    // حتى نضمن أن لا يتم إستدعاءها مرتين start() سنستخدم هذا المتغير كشرط أساسي في الدالة
                    private boolean isStarted = false;


                    // start() للدالة Override هنا فعلنا
                    @Override
                    public void start() {

                    // false تساوي isStarted إذا كانت قيمة المتغير
                    if (isStarted == false) {
                    // Thread و المسؤولة عن تشغيل كائن الـ Thread الموجودة في الكلاس start() سيتم إستدعاء الدالة
                    // مرتين Thread التي تشغل كائن الـ start() حتى لا يتم تنفيذ الدالة isStarted بعدها سيتم تغيير قيمة المتغير
                    super.start();
                    isStarted = true;
                    }
                    else {
                    resume();
                    }

                    }


                    @Override
                    public void run() {

                    // true ترجع isInterrupted() طالما أن الدالة
                    while (!this.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();

                    // هنا قمنا بطباعة شكل لائحة من الخيارات أمام المستخدم
                    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() بواسطة الدالة alert() هذه الحلقة تستمر في تنفيذ ما في داخلها طالما أنه لم يتم إيقاف الكائن
                    while(!alert.isInterrupted())
                    {
                    // هنا سيتم إنتظار المستخدم ليدخل رقم الخيار الذي يريد
                    System.out.print("User input >> ");
                    userInput = input.nextInt();

                    // هنا سيتم فحص القيمة التي أدخلها المستخدم
                    switch(userInput)
                    {
                    // في حال قام بإدخال الرقم 1 سيتم تشغيل التنبيه
                    case 1:
                    alert.start();
                    break;

                    // في حال قام بإدخال الرقم 2 سيتم إيقاف التنبيه مع إمكانية تشغيله من جديد
                    case 2:
                    alert.suspend();
                    break;

                    // في حال قام بإدخال الرقم 3 سيتم إعادة التنبيه للعمل من جديد
                    case 3:
                    alert.resume();
                    break;

                    // في حال قام بإدخال الرقم 4 سيتم إيقاف التنبيه مع عدم إمكانية تشغيله من جديد
                    case 4:
                    alert.interrupt();
                    alert.stop();
                    break;

                    // في حال قام بإدخال رقم أكبر من 4 أو أصغر من واحد سيتم إظهار خطأ له
                    default:
                    System.out.println("Option <"+userInput+"> Not Found!");
                    break;
                    }

                    }

                    }

                    }
                  

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

----- Alert Menu ------
| Enter (1) to Start&nbsp |
| Enter (2) to Pause&nbsp |
| Enter (3) to Resume |
| Enter (4) to Stop&nbsp&nbsp |
-----------------------
User input >> 1
User input >> 2
User input >> 3
User input >> 2
User input >> 3
User input >> 4
_______&&&______;&&&___________;&&&&________;

 الإنترفيس 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: &nbsp &nbsp&nbsp 7
Number of correct answers: 5
Number of wrong answers: &nbsp 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&nbsp |
| Enter (2) to Pause&nbsp |
| Enter (3) to Resume |
| Enter (4) to Stop&nbsp&nbsp |
-----------------------
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.

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

_______&&&______;&&&___________;&&&&________;

 الكلاس ThreadGroup في جافا

مقدمة

الكلاس ThreadGroup يجعلك قادراً على التحكم بطريقة عمل مجموعة كائنات Thread دفعة واحدة.

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

كائن الـ ThreadGroup الواحد يستطيع إحتواء عدة كائنات Thread, بالإضافة إلى أنه قادر على إحتواء كائنات ThreadGroup أيضاً.


بناؤه

                  public class ThreadGroup
                  extends Object
                  implements Thread.UncaughtExceptionHandler
                

التعامل مع الكلاس ThreadGroup

لجعل كائن الـ Thread ينتمي لكائن ThreadGroup محدد ( أي لمجموعة محددة ), عليك ربطه به ككائن نوعه Runnable.


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

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

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

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


مثال

MyRunnable.java
                    public class MyRunnable implements Runnable {

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

                    }
                  


خطوات إنشاء كائن من الكلاس ThreadGroup لإدارة عمل مجموعة كائنات من الكلاس Thread

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

  1. إنشاء كائن من الكلاس ThreadGroup, أي إنشاء مجموعة جديدة.

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

  3. إنشاء كائن من الكلاس Thread, و تمرير كائن الـ Runnable و كائن الـ ThreadGroup كـ Arguments له.

  4. تشغيل كائن الـ Thread بواسطة الدالة start().

  5. بعدها يمكنك التعامل مع الكائن ThreadGroup للتحكم بجميع كائنات الـ Thread التي تنتمي إليه.


مثال

MainThreadGroup.java
                    public class MainThreadGroup {

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

                    ThreadGroup tg = new ThreadGroup("tg group");     // tg group و قمنا بتسمية هذه المجموعة tg إسمه ThreadGroup هنا قمنا بإنشاء كائن من الكلاس

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

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

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

                    }

                    } 
                  

كونستركتورات الكلاس ThreadGroup

في الجدول التالي ذكرنا جميع كونستركتورات الكلاس ThreadGroup.

الكونستركتور مع تعريفه
public ThreadGroup(String name) ينشئ كائن نوعه ThreadGroup مع تحديد إسمه.
الأب لهذا الكائن يكون الـ Thread الحالي الذي قام بإنشائه.
يرمي الإستثناء SecurityException في حال كان الـ Thread الحالي لا يملك صلاحية إنشاء كائن ThreadGroup.
public ThreadGroup(ThreadGroup group, String name) ينشئ كائن نوعه ThreadGroup و يضعه ضمن مجموعة محددة, مع تحديد إسمه.

باراميترات
  • group عبارة عن كائن نوعه ThreadGroup يمثل المجموعة التي سيتم وضع هذا الكائن فيها.

  • name عبارة عن إسم يتم إعطاءه لكائن الـ ThreadGroup الذي سيتم إنشاءه و وضعه ضمن المجموعة المذكورة.


يرمي الإستثناء SecurityException في حال كان كائن الـ Thread الحالي لا يملك صلاحية إنشاء كائن ThreadGroup جديد بداخل المجموعة المشار إليها.

دوال الكلاس ThreadGroup

الجدول التالي يحتوي على دوال الكلاس ThreadGroup.

الدالة مع تعريفها
public final String getName() ترجع إسم كائن الـ ThreadGroup.
public String toString() ترجع نص يمثل معلومات كائن الـ ThreadGroup الذي قام باستدعائها. المعلومات عبارة عن ( Priority - Name ).
  • Name: هو إسم كائن الـ ThreadGroup.

  • Priority: هو أولية التفيذ المعطاة لكائن الـ ThreadGroup.

public final ThreadGroup getParent() ترجع كائن نوعه ThreadGroup يمثل الـ ThreadGroup الأب الذي تم فيه إنشاء هذا الكائن أو المجموعة التي ينتمي لها كائن ThreadGroup الذي قام باستدعائها.
public int activeCount() تستخدم لمعرفة عدد الـ threads الذين يعملون ضمن كائن الـ ThreadGroup لحظة استدعائها.
تستخدم فقط في حالة الـ Debugging.
public final void setDaemon(boolean on) تستخدم لتحويل كائن الـ ThreadGroup الذي قام باستدعائها إلى Daemon ThreadGroup.
الـ Daemon ThreadGroup يستدعي الدالة destroy() عندما لا يبقى أي Thread في حالة التنفيذ.
في حال قمت بتمرير القيمة true لها كـ Argument, سيتم معاملة كائن الـ ThreadGroup كــ Daemon ThreadGroup.
غير ذلك سيتم إعتباره كائن ThreadGroup عادي.

ترمي الإستثناء IllegalArgumentException في حال كان كائن الـ ThreadGroup شغالا أثناء استدعاءها.
و ترمي الإستثناء SecurityException في حال كان كائن الـ ThreadGroup الحالي لا يملك صلاحية تغييرها.
public final boolean isDaemon() تستخدم لمعرفة إذا كان كائن الـ ThreadGroup الذي قام باستدعائها هو Daemon ThreadGroup أم لا.
ترجع true إذا كان كذلك, غير ذلك ترجع false.
public final void suspend() توقف تنفيذ أوامر جميع كائنات الـ Thread و الـ ThreadGroup التابعة للكائن ThreadGroup الذي قام باستدعائها لمدة غير محددة, بحيث لا يمكنهم العودة للعمل من جديد إلا إذا قام باستدعاء الدالة resume() بعدها.
ترمي الإستثناء SecurityException في حال كان كائن الـ ThreadGroup لا يملك صلاحية التعديل على كائنات الـ Thread أو الـ ThreadGroup التابعة له و المتوقفة عن العمل.
public final void resume() تكمل تنفيذ أوامر جميع كائنات الـ Thread و الـ ThreadGroup التابعة للكائن ThreadGroup بعد أن كان قد قام بإيقاف تنفيذهم في حال قام بإيقافهم بواسطة الدالة suspend().
ترمي الإستثناء SecurityException في حال كان كائن الـ ThreadGroup لا يملك صلاحية التعديل على كائنات الـ Thread أو الـ ThreadGroup التابعة له و المتوقفة عن العمل.
public final void stop() توقف جميع كائنات الـ Thread و الـ ThreadGroup التابعة للكائنThreadGroup الذي قام باستدعائها, بحيث لا يمكنهم العودة للعمل من جديد.
ترمي الإستثناء SecurityException في حال كان كائن الـ ThreadGroup لا يملك صلاحية التعديل على كائنات الـ Thread أو الـ ThreadGroup التابعة له و المتوقفة عن العمل.
public final void interrupt() ترسل إشارة إلى الـ JVM لمقاطعة عمل جميع كائنات الـ Thread و الـ ThreadGroup التابعة للكائن ThreadGroup الذي قام باستدعائها.
في حال كان كائن الـ ThreadGroup غير شغال بعد لا تؤثر عليه أيضاً.

ترمي الإستثناء SecurityException في حال كان كائن الـ ThreadGroup الحالي لا يملك صلاحية التعديل على كائنات الـ Thread و الـ ThreadGroup التابعة له.

مثال شامل

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

ثم قمنا بإنشاء كلاس إسمه Main لتجربة إنشاء ThreadGroup و وضع كائنات من الـ Thread فيه.


MyRunnable.java
                    public class MyRunnable implements Runnable {

                    @Override
                    public void run() {
                    // true ترجع isInterrupted() طالما أن الدالة
                    while(!Thread.currentThread().isInterrupted())
                    {
                    // الذي يتم تنفيذ محتواه في الوقت الحالي Thread سيتم طباعة إسم كائن الـ
                    System.out.println(Thread.currentThread().getName());
                    }
                    }

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    // tg إسمه ThreadGroup هنا قمنا بإنشاء كائن من الكلاس
                    ThreadGroup tg = new ThreadGroup("tg group");

                    // tg ثم جعلناه ينتمي لمجموعة الكائن t1 إسمه Thread و ربطناه بكائن من الكلاس MyRunnable هنا قمنا بإنشاء كائن من الكلاس
                    Thread t1 = new Thread(tg, new MyRunnable(), "Thread 1");

                    // tg ثم جعلناه ينتمي لمجموعة الكائن t2 إسمه Thread و ربطناه بكائن من الكلاس MyRunnable هنا قمنا بإنشاء كائن من الكلاس
                    Thread t2 = new Thread(tg, new MyRunnable(), "Thread 2");

                    // t1 هنا قمنا بتشغيل الكائن
                    t1.start();

                    // t2 هنا قمنا بتشغيل الكائن
                    t2.start();

                    // يعدها سيتم إيقاف تنفيذ باقي الأوامر الموجودة في البرنامج مدة ثانية واحدة
                    try {
                    Thread.sleep(1000);
                    }
                    catch(Exception e) {
                    System.out.println(e.getMessage());
                    }

                    // tg تابع للكائن Thread بعد إنقضاء الثانية سيتم إيقاف تنفيذ كل
                    tg.interrupt();

                    // في الأخير سيتم عرض الجملة التالية
                    System.out.println("All Threads in 'tg group' are interrupted!");

                    }

                    }
                  

سنحصل على نتيجة تشبه النتيجة التالية عند التشغيل.

                    Thread 2
                    Thread 1
                    Thread 1
                    Thread 2
                    Thread 2
                    Thread 2
                    Thread 1
                    Thread 2
                    Thread 2
                    Thread 2
                    Thread 2
                    Thread 1
                    Thread 1
                    Thread 1
                    All Threads in 'tg group' are interrupted!
                  
_______&&&______;&&&___________;&&&&________;

 مثال حول فكرة الـ Thread Pool في جافا

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

ثم قمنا بإنشاء كلاس إسمه Main, و الذي قمنا فيه بإنشاء كائن نوعه ExecutorService إسمه executor يمثل خمس نسخ من الكلاس Thread.

بعدها قمنا بإنشاء حلقة for تعيد تنفيذ الأوامر التي بداخلها خمس مرات.
في كل مرة سيتم جعل الكائنات الخمسة الموجودة في الكائن executor تمثل خمسة كائنات من الكلاس MyRunnable, و سيتم تشغيلهم مباشرةً.

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


MyRunnable.java
                    public class MyRunnable implements Runnable {

                    @Override
                    public void run() {

                    // هنا قمنا بإنشاء حلقة تعيد تنفيذ ما بداخلها ثلاث مرات
                    for (int i=1; i<=3; i++)
                    {
                    // الحالي و عدد المرات التي تم تنفيذه فيها Thread هنا سيتم طباعة إسم كائن الـ
                    System.out.println(Thread.currentThread().getName() +", counter = "+ i);

                    // بعدها سيتم إيقافه مدة ثانية واحدة
                    try {
                    Thread.sleep(1000);
                    }
                    catch(Exception e) {
                    System.out.println(e.getMessage());
                    }
                    }

                    }

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    // فقط executor يمكن الوصول إليهم من خلال الكائن Thread هنا قمنا بإنشاء 5 نسخ من الكلاس
                    ExecutorService executor = Executors.newFixedThreadPool(5);

                    // هنا قمنا بإنشاء حلقة تعيد تنفيذ ما بداخلها 5 مرات
                    for (int i=1; i<=5; i++)
                    {
                    // و سيتم تشغيلهم MyRunnable كائنات من الكلاس executor هنا سيتم إعتبار الكائنات الخمسة الموجودة في الكائن
                    executor.execute(new MyRunnable());
                    }

                    // executor في الأخير سيتم إيقاف تنفيذ أوامر الكائنات الخمسة الموجودة في الكائن
                    executor.shutdown();

                    }

                    }
                  

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

                    pool-1-thread-2, counter = 1
                    pool-1-thread-4, counter = 1
                    pool-1-thread-1, counter = 1
                    pool-1-thread-3, counter = 1
                    pool-1-thread-5, counter = 1
                    pool-1-thread-2, counter = 2
                    pool-1-thread-5, counter = 2
                    pool-1-thread-1, counter = 2
                    pool-1-thread-4, counter = 2
                    pool-1-thread-3, counter = 2
                    pool-1-thread-4, counter = 3
                    pool-1-thread-3, counter = 3
                    pool-1-thread-1, counter = 3
                    pool-1-thread-5, counter = 3
                    pool-1-thread-2, counter = 3
                  
_______&&&______;&&&___________;&&&&________;