English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Java provides built-in support for multithreading programming. Thread refers to a single sequence of control flow in a process, and multiple threads can be concurrently executed in a process, each thread executing a different task.
المشاريع المتعددة هي شكل خاص من المشاريع المتعددة المهام، ولكن المشاريع المتعددة تستخدم تكاليف أقل من الموارد.
هنا يتم تعريف مفهوم آخر مرتبط بالمنشط - العملية: تشمل العملية مساحة ذاكرة تُ分配ها من قبل نظام التشغيل، تحتوي على thread واحد أو أكثر. لا يمكن للمنشط أن يكون موجودًا بشكل مستقل، يجب أن يكون جزءًا من العملية. تنتهي العملية عندما تنتهي جميع threads غير الحفاظية.
المشاريع المتعددة يمكن أن تسمح للمبرمجين بكتابة برامج فعالة لاستخدام CPU بشكل كامل.
المنشط هو عملية تنفيذ ديناميكية، له أيضًا عملية من الولادة إلى الموت.
الشكل أدناه يوضح دورة حياة المنشط بشكل كامل.
حالة الإنشاء:
استخدام new كلمة المفتاح و Thread عند إنشاء كائن منشط من فئة أو فرعها، يدخل كائن المنشط في حالة الإنشاء. يبقى في هذه الحالة حتى يُحقق البرنامج. start() هذا المنشط.
حالة الاستعداد:
عندما يُطلق على كائن منشط start()، يدخل المنشط في حالة الاستعداد. المنشط في حالة الاستعداد يتواجد في قائمة الاستعداد، ينتظر توجيه من مدير منشط JVM.
حالة التشغيل:
إذا حصل المنشط في حالة الاستعداد على موارد CPU، يمكنه تنفيذ run()، عند ذلك يكون المنشط في حالة التشغيل. المنشط في حالة التشغيل هو أكثر تعقيدًا، حيث يمكن أن يتحول إلى حالة المنع، حالة الاستعداد وحالة الإنهاء.
حالة المنع:
إذا قام المنشط بتنفيذ طرق مثل sleep (نوم) أو suspend (إلغاء الاشتراك) ولم يعد يمتلك الموارد التي يسيطر عليها، ينتقل المنشط من حالة التشغيل إلى حالة المنع. يمكنه العودة إلى حالة الاستعداد عند انتهاء وقت النوم أو الحصول على موارد الأجهزة. يمكن تصنيفها إلى ثلاثة أنواع:
المنع في الحالة الانتظار: عند تنفيذ المنشط لطريقة wait() في حالة التشغيل، يدخل المنشط في حالة المنع.
المنع في الحالة المتزامنة: عندما يفشل المنشط في الحصول على قفل المتزامنة synchronized (بسبب أن القفل المتزامن مكتسب من منشط آخر).
المنع في الحالة الأخرى: عند إرسال طلب I/O عبر دعوة المنشط إلى sleep() أو join()، يدخل المنشط في حالة المنع. عند انتهاء وقت wait()، أو عند انتظار إنهاء المنشط أو انتهاء وقت التأخير، أو انتهاء معالجة I/O، يعود المنشط إلى حالة الاستعداد.
حالة الموت:
عندما يكمل منشط في حالة التشغيل مهمته أو يحدث شرط إنهاء آخر، ينتقل المنشط إلى حالة الإنهاء.
كل منشط Java يمتلك مستوى أولوية، مما يساعد نظام التشغيل في تحديد ترتيب توجيه المنشط.
مستوى اولوية منشط Java هو عدد صحيح، مدى قيمته هو من 1 (Thread.MIN_PRIORITY) إلى 10 (Thread.MAX_PRIORITY).
بالتقديم، يتم تخصيص أولوية لكل فرع هي NORM_PRIORITY (5).
الفرع ذو الأولوية العالية مهم للبرنامج، ويجب أن يتم تخصيص موارد المعالج له قبل الفرع ذو الأولوية المنخفضة. ولكن، لا يمكن ضمان ترتيب تنفيذ الفرع بناءً على الأولوية، وهو يعتمد بشكل كبير على المنصة.
يقدم Java ثلاث طرق لإنشاء فرع:
من خلال تحقق من واجهة Runnable؛
من خلال توريث فئة Thread نفسها؛
إنشاء فرع من خلال Callable و Future.
أكثر طريقة بسيطة لإنشاء فرع هي إنشاء فئة تحقق من واجهة Runnable.
لتحقيق Runnable، يجب على الفئة تنفيذ طريقة واحدة فقط هي run()، كما يلي:
public void run()
يمكنك تعديل هذه الطريقة، والشيء المهم هو فهم أن run() يمكن أن يدعو إلى طرق أخرى، يستخدم كلاً من الفئات الأخرى، ويُعلن المتغيرات، مثل الفرع الرئيسي.
بعد إنشاء فئة تحقق من واجهة Runnable، يمكنك إنشاء مثال على كائن الفرع.
Thread يحدد عدة طرق بناء، والذي نستخدمه غالبًا هو كما يلي:
Thread(Runnable threadOb, String threadName);
في هذا السياق، threadOb هو مثال على فئة تحقق من واجهة Runnable، وthreadName يحدد اسم الفرع الجديد.
بعد إنشاء الفرع الجديد، يجب عليك تفعيل طريقة start() حتى يتم تشغيله.
void start();
إليك مثال على إنشاء فرع وبدء تنفيذه:
class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo(String name) { threadName = name; System.out.println("إنشاء " + threadName); {} public void run() { System.out.println("تشغيل " + threadName); try { for (int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // السكين يرقد قليلاً catch (InterruptedException e) { {} } System.out.println("Thread " + threadName + " معطل."); {} System.out.println("Thread " + threadName + " مغادرة."); {} public void start() { System.out.println("بداية " + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); {} {} {} public class TestThread { public static void main(String args[]) { RunnableDemo R1 = new RunnableDemo("Thread-1"); R1.start(); RunnableDemo R2 = new RunnableDemo("Thread-2"); R2.start(); {} {}
نتائج تشغيل البرنامج المسبق المذكور كالتالي:
إنشاء ثنائي-1 بداية ثنائي-1 إنشاء ثنائي-2 بداية ثنائي-2 التنفيذ ينتظر Thread-1 Thread: Thread-1, 4 التنفيذ ينتظر Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 يتخلص. Thread Thread-2 يتخلص.
طريقة ثانية لإنشاء فرع هي إنشاء فئة جديدة، تورث من فئة Thread، ثم إنشاء مثال من هذه الفئة.
الصفات الموروثة يجب أن تعيد كتابة طريقة run()، وهي نقطة الدخول للفرع الجديد. يجب عليها أيضًا أن تُطلق طريقة start() لكي تنفذ.
该方法 على الرغم من أن يتم تصنيفها كطريقة تنفيذ متعددة الأنماط، إلا أنها في الأساس هي مثال على تنفيذ واجهة Runnable.}
class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo(String name) { threadName = name; System.out.println("إنشاء " + threadName); {} public void run() { System.out.println("تشغيل " + threadName); try { for (int i = 4; i > 0; i--) { System.out.println("Thread: " + threadName + ", " + i); // السكين يرقد قليلاً catch (InterruptedException e) { {} } System.out.println("Thread " + threadName + " معطل."); {} System.out.println("Thread " + threadName + " مغادرة."); {} public void start() { System.out.println("بداية " + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); {} {} {} public class TestThread { public static void main(String args[]) { ThreadDemo T1 = new ThreadDemo("Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo("Thread-2"); T2.start(); {} {}
نتائج تشغيل البرنامج المسبق المذكور كالتالي:
إنشاء ثنائي-1 بداية ثنائي-1 إنشاء ثنائي-2 بداية ثنائي-2 التنفيذ ينتظر Thread-1 Thread: Thread-1, 4 التنفيذ ينتظر Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 يتخلص. Thread Thread-2 يتخلص.
في الجدول أدناه، تم ذكر بعض الطرق المهمة لـ Thread.
الرقم | وصف الطريقة |
---|---|
1 | public void start() يتم بدء تنفيذ السطر.;Java يتم استدعاء طريقة run للسطر من قبل محرك الربوت. |
2 | public void run() إذا كان السطر تم إنشاؤه باستخدام Runnable المستقل، يتم استدعاء طريقة run لهذاRunnable؛ وإلا، لا يتم تنفيذ أي شيء وتتم إرجاع. |
3 | public final void setName(String name) تغيير اسم السطر ليتطابق مع اسم المعامل name. |
3 | public final void setPriority(int priority) تغيير أولوية السطر. |
5 | public final void setDaemon(boolean on) يتم وضع السطر كسطر مراقب أو سطر مستخدم. |
6 | public final void join(long millisec) يتم انتظار انتهاء السطر لفترة أقصاها millis ميلي ثانية. |
7 | public void interrupt() يتم إيقاف السطر. |
8 | public final boolean isAlive() تستخدم لتحديد ما إذا كانت السطر�� أو لا. |
تستخدم لتحديد ما إذا كانت السطر�� أو لا. الطرق المذكورة أعلاه يتم استدعاؤها من قبل سطر Thread. الطرق التالية هي طرق ثابتة لـ Thread.
الرقم | وصف الطريقة |
---|---|
1 | public static void yield() يتوقف السطر الحالي عن التنفيذ ويتم تنفيذ سطر آخر. |
2 | public static void sleep(long millisec) في غضون الميلي ثانية المحددة، يتعطل نطاق العمل الحالي للسطر الحالي (يتوقف عن التنفيذ)، وقد تأثر هذا العمل ب دقة وتحديد جهاز التوقيت وبرنامج التخطيط النظامي. |
3 | public static boolean holdsLock(Object x) يعود إلى true إذا كان السطر الحالي يحمل قفل على العنصر المحدد فقط. |
3 | public static Thread currentThread() يعود إلى مرجع لسطر موجود حاليًا. |
5 | public static void dumpStack() طباعة تتبع الدعوة الحالية للسطر إلى منفذ الخطأ القياسي. |
برنامج ThreadClassDemo التالي يعرض بعض طرق كلاس Thread:
// اسم الملف: DisplayMessage.java // من خلال تنفيذ واجهة Runnable إنشاء سطر public class DisplayMessage implements Runnable { private String message; public DisplayMessage(String message) { this.message = message; {} public void run() { while(true) { System.out.println(message); {} {} {}
// اسم الملف: GuessANumber.java // من خلال توريث كلاس Thread إنشاء سطر public class GuessANumber extends Thread { private int number; public GuessANumber(int number) { this.number = number; {} public void run() { int counter = 0; int guess = 0; do { guess = (int) (Math.random() * 100 + 1); System.out.println(this.getName() + "الترميزات " + guess); counter++; } System.out.println("** الصحيح!" + this.getName() + "في" + counter + "الترميزات.**"); {} {}
// 文件名 : ThreadClassDemo.java public class ThreadClassDemo { public static void main(String [] args) { Runnable hello = new DisplayMessage("Hello"); Thread thread1 = new Thread(hello); thread1.setDaemon(true); thread1.setName("hello"); System.out.println("Starting hello thread..."); thread1.start(); Runnable bye = new DisplayMessage("Goodbye"); Thread thread2 = new Thread(bye); thread2.setPriority(Thread.MIN_PRIORITY); thread2.setDaemon(true); System.out.println("Starting goodbye thread..."); thread2.start(); System.out.println("Starting thread3..."); Thread thread3 = new GuessANumber(27); thread3.start(); try { thread3.join(); catch(InterruptedException e) { System.out.println("Thread interrupted."); {} System.out.println("Starting thread4..."); Thread thread4 = new GuessANumber(75); thread4.start(); System.out.println("main() is ending..."); {} {}
نتائج التنفيذ كما يلي، لا تُختلف نتائج كل تنفيذ.
بداية hello thread... بدء نواة وداعاً... مرحباً مرحباً مرحباً مرحباً مرحباً مرحباً وداعاً وداعاً وداعاً وداعاً وداعاً ......
1. قم بإنشاء فئة تعمل كـ Callable، وأحيل طريقة call()، والتي ستكون جسم النواة للفرع الفرعي، ولديها قيمة عودة.
2. قم بإنشاء مثال على فئة تعمل كـ Callable، واستخدم فئة FutureTask لتغليف كائن Callable، حيث يحتوي كائن FutureTask على نتائج طريقة call() للـ Callable.
3. استخدم كائن FutureTask كهدف لإنشاء وتشغيل نواة جديدة.
4. استدعاء طريقة get() من كائن FutureTask للحصول على القيمة التي يعود بها الفرع الفرعي عند اكتمال تنفيذه.
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName() + " 的循环变量i的值"+i); if(i==20) { new Thread(ft,"有线程返回值的线程").start(); {} {} try { System.out.println("子线程的返回值:"+ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); {} {} @Override public Integer call() throws Exception { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName() + " " + i); {} return i; {} {}
1. عند إنشاء أطراف باستخدام طريقة تحقيق Runnable أو Callable، فإن فئة الطرف تُحقق فقط لـ Runnable أو Callable، ويمكنها أيضًا الوراثة من فئات أخرى.
2. عند إنشاء أطراف باستخدام طريقة الوراثة لفئة Thread، فإن الفئة الخاصة بالطرف تُحقق فقط لـ Runnable أو Callable، ويمكنها أيضًا الوراثة من فئات أخرى.
عند برمجة الأطراف، يجب أن تعرف بعض المفاهيم التالية:
التنسيق بين الأطراف
الاتصال بين الأطراف
تلازم الأطراف
تحكم في الأطراف: تعليق، توقف وإعادة التشغيل
السر في استخدام الأطراف هو فهم أن البرنامج يتم تنفيذه بشكل متوازي وليس بشكل متسلسل. على سبيل المثال: إذا كان هناك نظامان تحتاجان إلى التنفيذ المتوازي، في هذه الحالة يجب استخدام برمجة الأطراف.
من خلال استخدام الأطراف، يمكنك كتابة برامج فعالة جدًا. ولكن، يرجى الانتباه أنه إذا قمت بإنشاء الكثير من الأطراف، فإن كفاءة تنفيذ البرنامج ستقل وليس ستزيد.
تذكر أن تكلفة التبديل بين السياقات مهمة أيضًا، إذا قمت بإنشاء الكثير من الأطراف، سيقضي CPU وقتًا أكثر في تبديل السياقات من وقت تنفيذ البرنامج!