متدها و چرخه حیات یک نخ یا Thread در جاوا

java-multithreading-methods-lifecycle

در قسمت اول سری آموزش برنامه نویسی چندنخی (MultiThreading) در جاوا، مقدمه ای در رابطه با نخ ها و روش ایجاد آن ها گفته شد، در این بخش قصد داریم متدهای مهم و پر کاربرد کلاس Thread و چرخه ی حیات یک نخ را بررسی کنیم.

متد های کلاس Thread

کلاس Thread دارای متدهای استاتیک و غیر استاتیک است.

1- متد استاتیک currentThread

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

Thread t1 = Thread.currentThread()

به مثال زیر توجه کنید.

public class Main  {
     public static void main(String[] args) {
      Thread tMain = Thread.currentThread();
      MyThread tt = new MyThread();
      Thread tMyThread= new Thread(tt,"T1");
      tMyThread.start();
      System.out.println("thread in MyThread : " + tMyThread.getName() +"\t"+ "Thread id : "+ tMyThread.getId());
      System.out.println("thread in Main : "+tMain.getName()+"\t"+ "Thread id : "+ tMain.getId()); 
  }
}
class MyThread implements Runnable{
    @Override
    public void run() { 
    }
}

در این مثال با استفاده از شی کلاس Thread و فراخوانی متد استاتیک currentThread، اطلاعات نخ اصلی Main را به دست می آوریم و با استفاده از دو متد Getname و Getid که دو متد کلاس Thread هستند، اطلاعات لازم را استخراج خواهیم کرد؛ در اینجا من نام نخ ایجاد شده از کلاس MyThread را T1 قرر داده ام.

خروجی مثال بالا به شکل زیر است.

خروجی
خروجی

2- متد استاتیک Sleep

الگوی استفاده از آن به صورت زیر است.

Thread.sleep()

نخ جاری که این متد را فراخوانی کرده برای مدت زمان مشخصی به خواب می رود که به دو صورت استفاده می شود:

  1. sleep(m) نخ را برای مدت زمان m میلی ثانیه به خواب می برد.
  2. sleep(m,n) نخ را برای مدت زمان m میلی ثانیه و n نانو ثانیه به خواب می برد.

لازم به ذکر است که استفاده از این متد Exception پرتاب می کند. (منظور از پرتاب Exception، در اختیار گذاشتن شرایطی است که برنامه نویس با استفاده از آن می تواند مدیریت خطا انجام دهد)

3- متد غیر استاتیک join

کاهی لازم است که کار یک نخ تمام شود تا نخ دیگر اجرا شود؛ به بیانی دیگر وقتی در اجرای یک برنامه چند نخ ایجاد می شوند شاید در مواردی لازم باشد مثلا نخ t1  به طور موقت متوقف شود تا نخ t2 وظیفه ی خود را به اتمام برساند و سپس نخ t1 به کار خود ادامه دهد.

به مثال زیر توجه کنید.

public class Main  {
     public static void main(String[] args) {
         MyThread obj = new MyThread();
         Thread t1 = new Thread(obj);
         Thread t2 = new Thread(obj);
         t1.start();
         t2.start();
  }
}
class MyThread implements Runnable{
    @Override
    public void run() { 
        for (int i = 1; i <= 50; i++) {
            System.out.println(Thread.currentThread().getName()+"\t"+i);
        }
    }
}

در اینجا بر روی شی کلاس MyThread دو نخ ایجاد شده است، که هر نخ وظیفه دارد اعداد 1 تا 50 را در خروجی چاپ کند و نام نخی که در حال حاضر در حال پردازش است، در خروجی نشان داده می شود.

این کد را اجرا کنید و خروجی را با خروجی کد زیر مقایسه کنید:

public class Main  {
     public static void main(String[] args) {
         MyThread obj = new MyThread();
         Thread t1 = new Thread(obj);
         Thread t2 = new Thread(obj);
         try {
             t1.start();
             t1.join();
             t2.start();
             t2.join();
         } catch (InterruptedException ex) {   
         }  
  }
}
class MyThread implements Runnable{
    @Override
    public void run() { 
        for (int i = 0; i <= 50; i++) {
            System.out.println(Thread.currentThread().getName()+"\t"+i);
        }
    }
}

نخ t1 شروع به کار می کند و نخ t2 تا زمان پایان کار t1 منتظر می ماند و بعد از آن شروع به اجرای وظیفه ی خود می کند. (برای درک بهتر این متد حتما خروجی هر دو کد را بررسی کنید)

لازم به ذکر است که استفاده از این متد Exception پرتاب می کند.

4- متدهای غیر استایک setPriority و getPriority 

متد setPriority اولویت نخ را مشخص می کند، در واقع سیستم عامل سعی می کند نخ هایی با اولویت بالا تر را بیشتر اجرا کند. به بیانی دیگر cpu زمان بیشتری را برای اجرای آن اختصاص می دهد.

ورودی متد اعداد 1 تا 10 است که 1 پایین ترین و 10 بالا ترین اولویت است.

الگوی استفاده ی آن ها به این صورت است.

ThreadObject.setPriority(int i)

و

int i= ThreadObject.getPriority()

در داخل کلاس Thread مقادیر public static final وجود دارد که به صورت زیر می توانند به عنوان پارامتر ورودی setPriority انتخاب شوند.

مقدار عددی نوع اولویت
1 MIN_PRIORITY
5 NORM_PRIORITY
10 MAX_PRIORITY

متد getPriority فاقد ورودی بوده و اولویت (اهمیت) نخ را به صورت int بر می گرداند.به مثال توجه کنید.

public class Main {
  public static void main(String[] args) {
    Thread t = Thread.currentThread();
    System.out.println("main Thread  Priority:" + t.getPriority());

    Thread t1 = new Thread();
    System.out.println("Thread(t1) Priority:" + t1.getPriority());

    t.setPriority(Thread.MAX_PRIORITY);
    System.out.println("main Thread  Priority:" + t.getPriority());

    Thread t2 = new Thread();
    System.out.println("Thread(t2) Priority:" + t2.getPriority());

    // Change thread t2 priority to minimum
    t2.setPriority(Thread.MIN_PRIORITY);
    System.out.println("Thread(t2) Priority:" + t2.getPriority());
  }
}

خروجی به شکل زیر است.

خروجی
خروجی

5- متدهای غیر استاتیک setDaemon و isDaemon

متد setDaemon که پارامتر ورودی آن به صورت boolean است، یک نخ را به عنوان شبح تعیین می کند.

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

متد isDeamon: اگر نخی از نوع شبح باشد مقدار true وگرنه مقدار false بر می گرداند.

الگوی استفاده از آن ها به صورت زیر است:

boolean b = ThreadObject.isDaemon()

و

ThreadObject.setDaemon(boolean b)

اگر کلیه ی نخ های برنامه وظیفه ی خود را انجام داده باشند و فقط نخ های شبح زنده باشند ماشین جاوا نخ های شبح را خاتمه می دهد و برنامه پایان می پذیرد.

برای ارائه ی یک مثال کاربردی از این دو متد نیاز به آشنایی بیشتر با مبحث multiThread است پس در جلسات آینده در جای مناسب مثالی در رابطه با آن بررسی می کنیم.

6- متد غیر استاتیک isAlive

الگوی استفاده ی آن به این صورت است:

boolean b = ThreadObject.isAlive()

این متد فاقد پارامتر ورودی بوده و خروجی آن به صورت boolean می باشد و وضعیت حیات نخ را اطلاع می دهد، به مثال زیر توجه کنید:

public class Main  {
     public static void main(String[] args) {
         MyThread obj = new MyThread();
         Thread t1 = new Thread(obj,"t1");
         Thread t2 = new Thread(obj,"t2");
         t1.start();
         t2.start();
         System.out.println("Thread t1 "+t1.isAlive());
         System.out.println("Thread t2 "+t2.isAlive());
         try {
             Thread.sleep(30);
         } catch (InterruptedException ex) {
         }
         System.out.println("Thread t1 "+t1.isAlive());
         System.out.println("Thread t2 "+t2.isAlive());
  }
}
class MyThread implements Runnable{
    @Override
    public void run() { 
            System.err.println(Thread.currentThread().getName()+"\t");      
    }
}

در این مثال ابتدا زنده بودن دو نخ بررسی می شود سپس با استفاده از متد sleep نخ اصلی main، مدت زمان 30 میلی ثانیه به خواب می رود تا از اتمام کار دو نخ t1 و t2 اطمینان حاصل شود و دوباره وضعیت حیات آن ها بررسی می شود که خروجی آن به شکل زیر است.

خروجی
خروجی

مشاهده می کنید که هر دو نخ در ابتدای اجرا زنده هستند ( مقدار true) و زمانی که وظیفه ی خود را به پایان می رسانند، عمر آن ها نیز به پایان می رسد(مقدار false).

6- متد های getName و setName

همان طور که از نام آن ها مشخص است برای نامگذاری و گرفتن نام یک نخ استفاده می شوند. متد getName در مثال های بالا بررسی شد اما متد setName به صورت الگوی زیر قابل استفاده است:

ThreadObject.setName(String name)

در ضمن در زمان ایجاد شی از نخ، نیز می توان به عنوان پارامتر ورودی اسم نخ را مشخص کرد، در این صورت نیازی به استفاده از متد setName نیست.

Thread T = new Thread("Thread_1")

در بالا بیشتر متدهای مهم کلاس Thread بررسی شد، باقی متدها در جای مناسب بررسی می شوند زیرا درک کاربرد آن ها، نیازمند فهم مطالب بیشتری است که در جلسات بعدی بررسی می شوند.

چرخه ی حیات یک Thread

مرحله اول: زمانی که  چندین شی جدید از کلاس Thread ایجاد می کنیم در واقع نخ های جدید متولد می شود و منتظر اجرای متد Start هستند.

مرحله ی دوم:وقتی متد Start هر نخ فراخوانی می شود در واقع هر نخ آماده ی اجرای وظیفه ی خود می شود، با توجه به امکانات سخت افزاری ممکن است چندین نخ با هم اجرا شوند. شاید هم در هر لحظه فقط یک نخ در حال اجرا باشد، که ترتیب اجرا شدن نخ ها وابسته به Priority آن ها است، پس هر نخی که اهمیت بیشتری داشته باشد سیستم عامل سعی می کند آن را زود تر اجرا کند و زمان بیشتری به آن اختصاص دهد؛ در این میان هم ممکن است نخ هایی وجود داشته باشند که منتظر پایان کار نخ های دیگر هستند تا بعد از اتمام کار آن ها وظیفه ی خود را انجام دهند، یا نخی که به sleep رفته یا متد join یا wait فراخوانی کرده یا منتظر ورود به بخش synchronized است؛ در پایان بخش انتظار، نخ به صف اجرا رفته و منتظر می مانند سیستم عامل آن را اجرا کند. به این بخش که نخ واقعا در حال اجرا است مرحله ی Runnable گفته می شود.

مرحله ی سوم: وقتی اجرای نخ به پایان برسد (دستورات متد Run تمام شود) دلیلی برای ادامه ی اجرای نخ نیست و به اصطلاح نخ می میرد، البته ذکر این نکته خالی از لطف نیست که در نسخه های جاوا 5 به بعد کلاس هایی وجود دارد، که زمانی که نخ آن ها وظیفه ی خود را انجام داد آن را به حالت نیمه مرده یا به اصطلاح غیر فعال نگه می دارند تا در زمان مورد نیاز از آن استفاده کنند. اهمیت این روش ایجاد نکردن دوباره یک نخ جدید است زیرا در برنامه های پیچیده تر ممکن است باعث افزایش هزینه و صرف وقت بیشتری شود. این موارد جز مباحث پیچیده Multi-Thread است و در جلسات پایانی این سری از آموزش ها بررسی خواهند شد.

مرحله ی میانی: ممکن است نخی که در مرحله ی دوم اجرا شده بنا به دلایلی در وسط اجرا به طور موقت غیر فعال شود (مثلا sleep ) و وقتی که انتظار نخ به پایان رسید دوباره به مرحله ی اجرا (Runnable) باز می گردد.

زمانی که نخ در وضعیت 1 و 2 می باشد، به اصطلاح به آن alive گویند.

چرخه حیات یا Lifecycle یک نخ (Thread) در جاوا
چرخه حیات یا Lifecycle یک نخ (Thread) در جاوا

عکس بالا کلیه ی مراحل ذکر شده را در بر دارد و گویای چرخه ی حیات نخ است.

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

موفق باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش برنامه نویسی MultiThreading در جاوا توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

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