أهلاً! اليوم سنواصل الحديث عن تعدد المواضيع. دعونا نتفحص فئة Thread وما تفعله بعض أساليبها. عندما درسنا طرق الفصل سابقًا، عادة ما نكتب ما يلي: <اسم الطريقة> -> <ما تفعله الطريقة>. لن ينجح هذا مع
Thread
توابع 's :) فهي تحتوي على منطق أكثر تعقيدًا لن تتمكن من اكتشافه بدون بعض الأمثلة.
طريقة Thread.start()
لنبدأ بتكرار أنفسنا. كما تتذكر على الأرجح، يمكنك إنشاء سلسلة رسائل عن طريق جعل فصلك يرث الفصلThread
وتجاوز run()
الطريقة. لكنها لن تبدأ من تلقاء نفسها بالطبع. للقيام بذلك، نسمي طريقة كائننا start()
. ولنتذكر المثال من الدرس السابق:
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
ملاحظة: لبدء سلسلة محادثات، يجب عليك استدعاءstart()
الطريقة الخاصة بدلاً منrun()
الطريقة! من السهل ارتكاب هذا الخطأ، خصوصًا عند البدء بدراسة تعدد مؤشرات الترابط لأول مرة. في مثالنا، إذا قمت باستدعاءrun()
الطريقة 10 مرات بدلاً منstart()
, فستحصل على ما يلي:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.run();
}
}
}
انظر إلى نتائج برنامجنا: تم تنفيذ الموضوع: Thread-0 تم تنفيذ الموضوع: Thread-1 تم تنفيذ الموضوع: Thread-2 تم تنفيذ الموضوع: Thread-3 تم تنفيذ الموضوع: Thread-4 تم تنفيذ الموضوع: Thread-5 تم تنفيذ الموضوع: Thread-6 تم تنفيذ الموضوع: Thread-7 تم تنفيذ الموضوع: Thread-8 تم تنفيذ الموضوع: Thread-9 انظر إلى ترتيب الإخراج: كل شيء يحدث بترتيب مثالي. غريب، هاه؟ نحن لسنا معتادين على ذلك، لأننا نعلم بالفعل أن الترتيب الذي يتم به بدء سلاسل الرسائل وتنفيذها يتم تحديده بواسطة ذكاء فائق داخل نظام التشغيل الخاص بنا: برنامج جدولة سلاسل الرسائل. ربما كنا محظوظين للتو؟ وبطبيعة الحال، هذا لا يتعلق بالحظ. يمكنك التحقق من ذلك عن طريق تشغيل البرنامج عدة مرات. المشكلة هي أن استدعاء run()
الطريقة مباشرة لا علاقة له بتعدد العمليات. في هذه الحالة، سيتم تنفيذ البرنامج على الخيط الرئيسي، وهو نفس الخيط الذي ينفذ الطريقة main()
. إنها ببساطة تطبع 10 أسطر على التوالي على وحدة التحكم وهذا كل شيء. 10 مواضيع لم تبدأ بعد. لذلك، تذكر هذا في المستقبل وتحقق من نفسك باستمرار. إذا كنت تريد run()
استدعاء الطريقة، فاتصل بـ start()
. دعنا نذهب أبعد من ذلك.
طريقة Thread.sleep()
لتعليق تنفيذ الخيط الحالي لفترة من الوقت، نستخدم هذهsleep()
الطريقة. تستغرق الطريقة sleep()
عددًا من المللي ثانية كوسيطة، مما يشير إلى مقدار الوقت اللازم لوضع مؤشر الترابط في وضع السكون.
public class Main {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(3000);
System.out.println(" - How long did I sleep? \n - " + ((System.currentTimeMillis()-start)) / 1000 + " seconds");
}
}
إخراج وحدة التحكم: - كم من الوقت نمت؟ - 3 ثواني ملاحظة: الطريقة sleep()
ثابتة: فهي تنام الخيط الحالي. أي الذي يتم تنفيذه حاليًا. إليك نقطة أخرى مهمة: يمكن مقاطعة خيط النوم. في هذه الحالة، يقوم البرنامج بطرح ملف InterruptedException
. سننظر في مثال أدناه. بالمناسبة، ماذا يحدث بعد استيقاظ الخيط؟ هل سيستمر تنفيذه من حيث توقف؟ لا. بعد تنبيه الخيط، أي مرور الوقت كوسيطة Thread.sleep()
، فإنه ينتقل إلى حالة قابلة للتشغيل . لكن هذا لا يعني أن برنامج جدولة الخيط سيقوم بتشغيله. من المحتمل جدًا أن يعطي الأفضلية لبعض الخيوط الأخرى غير النائمة ويسمح لخيطنا المستيقظ حديثًا بمواصلة عمله بعد قليل. تأكد من تذكر هذا: الاستيقاظ لا يعني مواصلة العمل على الفور!
طريقة Thread.join()
يوقف الأسلوبjoin()
تنفيذ مؤشر الترابط الحالي حتى انتهاء مؤشر ترابط آخر. إذا كان لدينا 2 المواضيع، t1
و t2
، ونكتب
t1.join()
ثم t2
لن يبدأ إلا بعد t1
الانتهاء من عمله. يمكن استخدام هذه join()
الطريقة لضمان ترتيب تنفيذ الخيوط. دعونا نفكر في كيفية join()
عمل الطريقة في المثال التالي:
public class ThreadExample extends Thread {
@Override
public void run() {
System.out.println("Thread started: " + getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + getName() + " is finished.");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadExample t1 = new ThreadExample();
ThreadExample t2 = new ThreadExample();
t1.start();
/* The second thread (t2) will start running only after the first thread (t1)
is finished (or an exception is thrown) */
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
// The main thread will continue running only after t1 and t2 have finished
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All threads have finished. The program is finished.");
}
}
أنشأنا ThreadExample
فئة بسيطة. وتتمثل مهمتها في عرض رسالة تفيد بأن الخيط قد بدأ، ثم النوم لمدة 5 ثوانٍ، ثم الإبلاغ في النهاية عن اكتمال العمل. قطعة من الكعك. المنطق الرئيسي موجود في Main
الفصل. انظر إلى التعليقات: نستخدم الطريقة join()
لإدارة ترتيب تنفيذ سلاسل الرسائل بنجاح. إذا كنت تتذكر كيف بدأنا هذا الموضوع، فسيتم التعامل مع أمر التنفيذ بواسطة برنامج جدولة الموضوع. يقوم بتشغيل المواضيع وفقًا لتقديره الخاص: في كل مرة بطريقة مختلفة. نحن هنا نستخدم هذه الطريقة لضمان t1
بدء تشغيل الخيط وتنفيذه أولاً، ثم t2
الخيط، وبعد ذلك فقط سيستمر الخيط الرئيسي للبرنامج. المضي قدمًا. في البرامج الحقيقية، ستجد غالبًا مواقف ستحتاج فيها إلى مقاطعة تنفيذ سلسلة رسائل. على سبيل المثال، مؤشر الترابط الخاص بنا قيد التشغيل، ولكنه ينتظر حدثًا أو شرطًا معينًا. إذا حدث ذلك، يتوقف الخيط. ربما سيكون الأمر منطقيًا إذا كان هناك نوع من stop()
الطريقة. لكن الأمر ليس بهذه البساطة. ذات مرة، كان لدى Java بالفعل Thread.stop()
طريقة تسمح بمقاطعة سلسلة المحادثات. ولكن تمت إزالته لاحقًا من مكتبة Java. يمكنك العثور عليه في وثائق Oracle والتأكد من أنه تم وضع علامة عليه كمهمل . لماذا؟ لأنه أوقف الخيط دون فعل أي شيء آخر. على سبيل المثال، قد يعمل مؤشر الترابط مع البيانات ويغير شيئًا ما. ثم، في منتصف عمله، تم قطعه فجأة وبشكل غير رسمي بهذه stop()
الطريقة. بدون إيقاف التشغيل بشكل مناسب، ولا تحرير الموارد، ولا حتى معالجة الأخطاء - لم يكن هناك أي من هذا. وللمبالغة قليلاً، فإن هذه stop()
الطريقة ببساطة دمرت كل شيء في طريقها. كان الأمر أشبه بسحب سلك الطاقة من مأخذ الطاقة لإيقاف تشغيل الكمبيوتر. نعم، يمكنك الحصول على النتيجة المرجوة. لكن الجميع يعلم أنه بعد بضعة أسابيع لن يشكرك الكمبيوتر على التعامل معه بهذه الطريقة. لهذا السبب تغير منطق مقاطعة سلاسل الرسائل في Java ويستخدم الآن interrupt()
طريقة خاصة.
طريقة Thread.interrupt()
ماذا يحدث إذاinterrupt()
تم استدعاء الطريقة على موضوع؟ هناك احتمالان:
- إذا كان الكائن في حالة الانتظار، على سبيل المثال، بسبب الأساليب
join
أوsleep
، فسيتم مقاطعة الانتظار وسيقوم البرنامج بطرح ملفInterruptedException
. - إذا كان الخيط في حالة عمل،
interrupted
فسيتم تعيين العلامة المنطقية على الكائن.
Thread
الفصل لديه boolean isInterrupted()
الطريقة. لنعود إلى مثال الساعة الذي كان في أحد دروس الدورة الأساسية. ولتسهيل الأمر، قمنا بتبسيط الأمر قليلاً:
public class Clock extends Thread {
public static void main(String[] args) throws InterruptedException {
Clock clock = new Clock();
clock.start();
Thread.sleep(10000);
clock.interrupt();
}
public void run() {
Thread current = Thread.currentThread();
while (!current.isInterrupted())
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("The thread was interrupted");
break;
}
System.out.println("Tick");
}
}
}
في هذه الحالة، تبدأ الساعة وتبدأ بالدق كل ثانية. في الثانية العاشرة نقطع خيط الساعة. كما تعلم بالفعل، إذا كان الخيط الذي نحاول مقاطعته في إحدى حالات الانتظار، فستكون النتيجة InterruptedException
. هذا استثناء محدد، لذا يمكننا التعرف عليه بسهولة وتنفيذ المنطق الخاص بنا لإنهاء البرنامج. وهذا بالضبط ما فعلناه. هذه هي النتيجة التي حصلنا عليها: ضع علامة Tick Tcik Tick Tick Tick Tick تمت مقاطعة الموضوع وبهذا نختتم مقدمتنا Thread
لأهم أساليب الفصل. حظ سعيد!
GO TO FULL VERSION