English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

تعليمية Java الأساسية

Java flow control

Java array

Java object-oriented (I)

Java object-oriented (II)

Java object-oriented (III)

معالجة الاستثناء في Java

Java List

Java Queue (queue)

Java Map collection

Java Set collection

Java input/output (I/O)

Java Reader/Writer

Java other topics

برمجة Java Multithreading

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.

لتحقيق 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

طريقة ثانية لإنشاء فرع هي إنشاء فئة جديدة، تورث من فئة 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

في الجدول أدناه، تم ذكر بعض الطرق المهمة لـ Thread.

الرقموصف الطريقة
                1public void start()
يتم بدء تنفيذ السطر.;Java يتم استدعاء طريقة run للسطر من قبل محرك الربوت.
                2public void run()
إذا كان السطر تم إنشاؤه باستخدام Runnable المستقل، يتم استدعاء طريقة run لهذاRunnable؛ وإلا، لا يتم تنفيذ أي شيء وتتم إرجاع.
                3public final void setName(String name)
تغيير اسم السطر ليتطابق مع اسم المعامل name.
                3public final void setPriority(int priority)
 تغيير أولوية السطر.
                5public final void setDaemon(boolean on)
يتم وضع السطر كسطر مراقب أو سطر مستخدم.
                6public final void join(long millisec)
يتم انتظار انتهاء السطر لفترة أقصاها millis ميلي ثانية.
                7public void interrupt()
يتم إيقاف السطر.
                8public final boolean isAlive()
تستخدم لتحديد ما إذا كانت السطر�� أو لا.

تستخدم لتحديد ما إذا كانت السطر�� أو لا. الطرق المذكورة أعلاه يتم استدعاؤها من قبل سطر Thread. الطرق التالية هي طرق ثابتة لـ Thread.

الرقموصف الطريقة
                1public static void yield()
يتوقف السطر الحالي عن التنفيذ ويتم تنفيذ سطر آخر.
                2public static void sleep(long millisec)
في غضون الميلي ثانية المحددة، يتعطل نطاق العمل الحالي للسطر الحالي (يتوقف عن التنفيذ)، وقد تأثر هذا العمل ب دقة وتحديد جهاز التوقيت وبرنامج التخطيط النظامي.
                3public static boolean holdsLock(Object x)
يعود إلى true إذا كان السطر الحالي يحمل قفل على العنصر المحدد فقط.
                3public static Thread currentThread()
يعود إلى مرجع لسطر موجود حاليًا.
                5public static void dumpStack()
طباعة تتبع الدعوة الحالية للسطر إلى منفذ الخطأ القياسي.

مثال علىpline

برنامج 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 ملف الكود:

// اسم الملف: 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 file code:

// 文件名 : 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...
بدء نواة وداعاً...
مرحباً
مرحباً
مرحباً
مرحباً
مرحباً
مرحباً
وداعاً
وداعاً
وداعاً
وداعاً
وداعاً
......

إنشاء نواة باستخدام Callable و Future

  • 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 وقتًا أكثر في تبديل السياقات من وقت تنفيذ البرنامج!