شنبه ۲۹ دي ۱۴۰۳
Tut24 آموزش برنامه نویسی و مجله تخصصی فناوری ورود/عضویت

آموزش جاوا - چندریختی

جاوا یک زبان برنامه‌نویسی چندنخی است که به این معناست که ما می‌توانیم برنامه‌های چندنخی را با استفاده از جاوا توسعه دهیم. یک برنامه چندنخی دو یا بیشتر بخش دارد که می‌توانند همزمان اجرا شوند و هر بخش می‌تواند در همان زمان وظیفه‌ای متفاوت را انجام دهد و از منابع موجود بهینه استفاده کند، به ویژه وقتی که کامپیوتر شما دارای چندین واحد پردازش است.

به طور تعریفی، چندکاری زمانی است که چندین فرآیند از منابع پردازشی مشترک مانند یک CPU استفاده کنند. چندنخی ایده چندکاری را در برنامه‌ها گسترش می‌دهد تا بتوانید عملیات‌های خاصی را در یک برنامه تقسیم کنید. هرکدام از این نخ‌ها می‌توانند به صورت همروند اجرا شوند. سیستم عامل زمان پردازش را نه تنها بین برنامه‌های مختلف تقسیم می‌کند، بلکه بین هر نخ در یک برنامه نیز تقسیم می‌کند.

چندنخی به شما امکان می‌دهد بنویسید به گونه‌ای که فعالیت‌های چندگانه می‌توانند همزمان در یک برنامه ادامه یابند.

دوره عمر یک نخ

یک نخ در طول عمر خود از مراحل مختلفی عبور می‌کند. به عنوان مثال، یک نخ متولد می‌شود، شروع می‌شود، اجرا می‌شود و سپس می‌میرد. نمودار زیر دوره عمر کامل یک نخ را نشان می‌دهد.

نخ جاوا

مراحل دوره عمر به شرح زیر است:

  • جدید − یک نخ جدید عمر خود را در حالت جدید آغاز می‌کند. در این حالت می‌ماند تا برنامه نخ را آغاز کند. به آن به عنوان یک نخ متولد شده نیز اشاره می‌شود.

  • اجراپذیر − بعد از شروع یک نخ تازه متولد شده، نخ به حالت اجراپذیر می‌رود. یک نخ در این حالت در حال اجرای وظیفه خود است.

  • انتظار − گاهی اوقات، یک نخ به حالت انتظار می‌رود در حالی که منتظر نخ دیگری برای انجام وظیفه است. نخ تنها زمانی به حالت اجراپذیر برمی‌گردد که نخ دیگری به نخ انتظاری علامت بدهد که اجرای خود را ادامه دهد.

  • انتظار با زمان − یک نخ قابل اجرا می‌تواند برای یک بازه زمانی مشخص به حالت انتظار زمان‌دار بپیوندد. نخ در این حالت به حالت اجراپذیر برمی‌گردد زمانی که زمان اینترول مشخص شده گذشت یا زمانی که رویدادی که منتظر آن است، رخ دهد.

  • پایان (مرده) − یک نخ قابل اجرا وارد حالت پایانی می‌شود وقتی که وظیفه‌اش را کامل می‌کند یا به طور دیگر خاتمه می‌یابد.

اولویت‌های نخ

هر نخ جاوا دارای یک اولویت است که به سیستم عامل کمک می‌کند تا ترتیب برنامه‌ها را برنامه‌ریزی کند.

اولویت‌های نخ جاوا در بازه MIN_PRIORITY (یک ثابت با مقدار ۱) و MAX_PRIORITY (یک ثابت با مقدار ۱۰) قرار دارند. به طور پیش فرض، هر نخ به اولویت NORM_PRIORITY (یک ثابت با مقدار ۵) اختصاص داده می‌شود.

نخ‌های با اولویت بالاتر برای برنامه مهم‌تر هستند و باید قبل از نخ‌های با اولویت پایین‌تر زمان پردازش را دریافت کنند. با این حال، اولویت نخ‌ها قادر به تضمین ترتیب اجرای نخ‌ها نیست و بسیار وابسته به سکوی پلتفرم است.

ایجاد یک نخ با پیاده‌سازی رابط Runnable

اگر کلاس شما قرار است به عنوان یک نخ اجرا شود، می‌توانید این کار را با پیاده‌سازی یک رابط Runnable انجام دهید. برای این منظور، باید سه مرحله اصلی را دنبال کنید:

مرحله 1

در مرحله اول، باید یک متد run() که توسط رابط Runnable ارائه شده است، پیاده‌سازی کنید. این متد نقطه ورودی برای نخ است و شما باید منطق کامل کسب و کار خود را در این متد قرار دهید. دستورالعمل ساده‌ای برای متد run() به شرح زیر است:


public void run( )

مرحله 2

در مرحله دوم، شما یک شیء Thread را با استفاده از سازنده زیر نمونه‌سازی می‌کنید:


Thread(Runnable threadObj, String threadName);

در اینجا، threadObj یک نمونه از یک کلاس است که رابط Runnable را پیاده‌سازی کرده است و threadName نامی است که به نخ جدید داده شده است.

مرحله 3

با ایجاد یک شیء نخ، می‌توانید آن را با فراخوانی متد start() شروع کنید، که تماسی با متد run() را اجرا می‌کند. دستورالعمل ساده‌ای برای متد start() به شرح زیر است:

.


void start();

مثال

در ادامه، یک مثال آورده شده است که یک نخ جدید را ایجاد می‌کند و آن را شروع به اجرا می‌کند:


class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  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();
   }   
}

این کار نتیجه زیر را تولید می‌کند:

خروجی


Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running 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 exiting.
Thread Thread-2 exiting.

ایجاد نخ با گسترش یک کلاس Thread

روش دوم برای ایجاد یک نخ، ایجاد یک کلاس جدید است که از کلاس Thread گسترش می‌یابد و از دو مرحله ساده زیر استفاده می‌کند. این روش امکانات بیشتری را برای کنترل نخ‌های متعددی که با استفاده از متدهای موجود در کلاس Thread ایجاد شده‌اند، فراهم می‌کند.

مرحله 1

باید متد run() موجود در کلاس Thread را override کنید. این متد نقطه ورودی برای نخ است و شما باید منطق کامل کسب و کار خود را در این متد قرار دهید. دستورالعمل ساده‌ای برای متد run() به شرح زیر است:


public void run( )

مرحله 2

با ایجاد شیء Thread، می‌توانید آن را با فراخوانی متد start() شروع کنید، که تماسی با متد run() را اجرا می‌کند. دستورالعمل ساده‌ای برای متد start() به شرح زیر است:


void start( );

مثال

در ادامه، برنامه‌ی قبلی که با گسترش کلاس Thread نوشته شده است را آورده‌ایم:


class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  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();
   }   
}

این کار نتیجه زیر را تولید می‌کند:

خروجی


Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running 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 exiting.
Thread Thread-2 exiting.

متدهای رشته‌ها (Thread Methods)

در ادامه، لیستی از متدهای مهمی که در کلاس رشته (Thread) موجود است آمده است.

شماره متد و توضیحات
1

public void start()

شروع رشته در یک مسیر اجرای جداگانه و سپس فراخوانی متد run() بر روی این شیء رشته (Thread).

2

public void run()

اگر شیء رشته (Thread) با استفاده از یک هدف Runnable جداگانه نمونه‌سازی شده باشد، متد run() بر روی آن شیء فراخوانی می‌شود.

3

public final void setName(String name)

تغییر نام شیء رشته (Thread). همچنین متد getName() برای دریافت نام وجود دارد.

4

public final void setPriority(int priority)

تنظیم اولویت این شیء رشته (Thread). مقادیر ممکن بین ۱ و ۱۰ است.

5

public final void setDaemon(boolean on)

اگر پارامتر true باشد، این شیء رشته (Thread) را به عنوان یک رشته خدمت (daemon thread) مشخص می‌کند.

6

public final void join(long millisec)

رشته فعلی این متد را بر روی یک رشته دیگر فراخوانی می‌کند و باعث می‌شود رشته فعلی بلاک شده و منتظر ترمینه شدن رشته دوم یا گذشت زمان مشخص شده شود.

7

public void interrupt()

متدی که این رشته را متوقف می‌کند و در صورت بلاک شدن هر دلیلی، اجرای آن را ادامه می‌دهد.

8

public final boolean isAlive()

در صورتی که رشته زنده باشد (alive)، یعنی هر زمانی که بعد از شروع رشته و قبل از تکمیل آن باشد، مقدار true برگردانده می‌شود.

متدهای قبلی بر روی یک شیء رشته خاص فراخوانی می‌شوند. متدهای زیر در کلاس رشته به صورت استاتیک وجود دارند. فراخوانی یکی از متدهای استاتیک عملیات مورد نظر را روی رشته فعلی اجرا می‌کند.

شماره متد و توضیحات
1

public static void yield()

باعث می‌شود رشته فعلی به رشته‌های دیگر با همان اولویت که در انتظار برنامه ریزی هستند، اولویت دهد.

2

public static void sleep(long millisec)

باعث می‌شود رشته فعلی حداقل تعداد میلی‌ثانیه مشخص شده بلاک شود.

3

public static boolean holdsLock(Object x)

اگر رشته فعلی قفل شیء داده شده را نگه داشته باشد، مقدار true برگردانده می‌شود.

4

public static Thread currentThread()

یک مرجع به رشته فعلی (رشته‌ای که این متد را فراخوانی می‌کند) برمی‌گرداند.

5

public static void dumpStack()

ردیابی پشته برای رشته فعلی را چاپ می‌کند که در بررسی و رفع اشکال یک برنامه چندرشته‌ای مفید است.

مثال

برنامه ThreadClassDemo زیر، برخی از این متدهای کلاس Thread را نشان می‌دهد. یک کلاس به نام DisplayMessage را در نظر بگیرید که Runnable را پیاده‌سازی می‌کند.


// File Name : DisplayMessage.java
// Create a thread to implement 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);
      }
   }
}

در ادامه یک کلاس دیگر وجود دارد که از کلاس Thread به ارث می‌برد.


// File Name : GuessANumber.java
// Create a thread to extentd 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() + " guesses " + guess);
         counter++;
      } while(guess != number);
      System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**");
   }
}

در ادامه برنامه اصلی آمده است که از کلاس‌های فوق استفاده می‌کند.


// File Name : 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...");
   }
}

این عمل، نتیجه زیر را تولید می‌کند. شما می‌توانید این مثال را مجدداً تکرار کنید و هر بار نتیجه‌ای متفاوت دریافت خواهید کرد.

خروجی


Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
.......

مفاهیم اصلی موضوع Multithreading در جاوا (Java)

در هنگام برنامه‌نویسی Multithreading در جاوا، شما باید با مفاهیم زیر آشنا باشید و آنها را در دسترس داشته باشید −