مقدمة
يستخدم الكلاس 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
- نفعل
extends
للكلاس Thread
.
- نفعل Override للدالة
run()
لنضع جميع الأوامر التي نريدها أن تتنفذ عند تشغيل كائن الـ Thread
.
مثال
MyThread.java
public class MyThread extends Thread {
@Override
public void run(){
// start() هنا تضع الأوامر التي تريد تنفيذها عند إستدعاء الدالة
}
}
خطوات إنشاء الـ Thread
و تشغيله
- ننشئ كائن من الكلاس الذي يرث من الكلاس
Thread
.
- نستدعي الدالة
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()
لمتابعة تنفيذ باقي الأوامر الموجودة فيها.
أمثلة شاملة
المثال الأول
المثال التالي عبارة عن برنامج يعرض الوقت الحالي للجهاز.
في البداية قمنا بإنشاء كلاس إسمه 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:      7
Number of correct answers: 5
Number of wrong answers:   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  |
| 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