مفهوم تعامل چند Thread با یکدیگر در جاوا و بررسی چند مثال

java-multithreading-thread-interaction

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

منظور از تعامل بین Thread ها چیست؟

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

برای این منظور جاوا دو متد Wait و notify را در کلاس Object قرار داده که با استفاده از این متدها می توان بین نخ ها تعامل یا به نوعی وابستگی ایجاد کرد.

به عنوان مثال یک آنتی ویروس را در نظر بگیرید که وقتی یک ویروس شناسایی کرد نام آن ویروس را نمایش می دهد و دوباره به ادامه ی جست و جو  می پردازد، در اینجا دو Thread را در نظر بگیرید، 1 Thread وظیفه ی جست وجوی ویروس و Thread 2وظیفه ی نشان دادن نام ویروس و مشخصات آن را بر عهده دارد، زمانی که 1 Thread در حال پیدا کردن ویروس است 2 Thread در وضعیت انتظار (Wait) به سر می برد تا زمانی که یک ویروس پیدا شود و 1 Thread به 2 Thread خبر دهد (notify) تا نام ویروس را نشان دهد، بعد از انجام وظیفه، 2 Thread دوباره به وضعیت انتظار (wait) می رود و این چرخه بارها و بارها اجرا می شود.

توجه: متدهای wait و notify در کلاس Object از نوع Final تعریف شده اند و نحوه ی پیاده سازی آن ها به صورت سطح پایین (native) است، پس به هیچ عنوان قابلیت Override را ندارد.

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

برای بررسی مثال و نحوه ی کار با این دو متد ابتدا لازم است چند نکته را بیان کنیم.

نکته:

1- متدهای wait و notify فقط در صورتی روی یک شی ( مثلا شی obj) قابل فراخوانی هستند که در یک بلوک synchronized obj قرار گرفته باشد، به عبارتی دیگر یک Thread برای این که بتواند بر روی یک شی متدهای wait و notify را فراخوانی کند باید قفل آن شی را قبلا در اختیار گرفته باشد وگرنه باعث بروز خطا می شود.

2- زمانی که obj.wait توسط شی فراخوانی می شود بلافاصله قفل obj آزاد می شود تا Thread های دیگر بتوانند وارد بلوک synchronized شده و متد obj.notify را صدا بزنند تا Thread قبلی از حالت انتظار خارج شود.

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

    synchronized void Example(){
    }
    point 1
        this.wait();
    point 2
    }
    synchronized (obj){
        obj.notify();
    }

در داخل یک متد synchronized متد wait روی شی this فراخوانی شده پس در زمان ورود به این متد روند اجرای برنامه در point 1 متوقف می شود تا در جایی دیگر در داخل بلوک synchronized بر روی همان شی notify فراخوانی شود که در این جا نام شی obj فرض شده است و سپس متد از point 2 شروع به ادامه ی فعالیت می پردازد.

3- متد wait به صورت (wait(ms (میلی ثانیه) نیز قابل استفاده است، که حداکثر زمان انتظار را به ms تعیین می کند، در صورت اتمام این زمان حتی اگر Thread دیگر روی آن شی notify  انجام ندهد Thread از حالت انتظار خارج می شود و به  ادامه ی فعالت خود می پردازد.

حالا کاربرد این دو متد را در مثال زیر بررسی می کنیم.

public class Main {
    public static void main(String[] args) {
        System.out.println("start Main");
        Scan scan = new Scan();
        Object Y=scan.X=new Object();
        scan.start();
        synchronized(Y){
            try {
                Y.wait();
            } catch (InterruptedException ex) {}
        }
         System.out.println("End of Main");     
    }
}
class Scan extends Thread{
      Object X;
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {}
        System.out.println("Start Scan");
        synchronized(X){
            X.notify();
        }
        System.out.println("Scan complete");
    }
}

در ابتدای کلاس scan،  یک شی با نام X از کلاس Object ایجاد شده و در داخل run بعد از 1 ثانیه مکث Start Scan چاپ شده و بر روی شی X در داخل بلوک synchronized متد notify فراخوانی و در نهایت بخش Scan complete اجرا می شود.

حال در متد Main ابتدا Start Main چاپ می شود و بعد از آن یک شی با نام scan از کلاس Scan ساخته شده است و بعد از آن یک شی از کلاس Object با نام Y ساخته شده که در نهایت با شی ساخته شده در داخل کلاس Scan مساوی قرار داده شده است و به نوعی مرجع هر دو شی یکسان است (X=Y).

در ادامه Thread مربوط به شی scan اجرا می شود. در ابتدای شروع برنامه قطعا start main چاپ می شود زیرا در نقطه ی شروع برنامه قرار گرفته است. خب بعد از آن چه چیزی اجرا می شود ؟ بلافاصله بعد از start شدن Thread شی scan، به مدت 1 ثانیه مکث می کند، در زمان توقف این نخ، نخ اصلی Main به اجرای خود ادامه می دهد تا به بلوک

synchronized(Y){
            try {
                Y.wait();
            } catch (InterruptedException ex) {}
        }

می رسد و Thread اصلی در همین نقطه ایست می کند و منتظر می شود تا یک Thread دیگر بر روی همین شی notify کند، در این میان مدت زمان مکث Thread کلاس Scan به پایان رسیده و Start Scan چاپ می شود و بعد از آن بر روی شی مشترک X متد notify را صدا می زند و بلافاصه Scan complete چاپ شده و Thread اصلی که منتظر بود، دوباره فعال می شود و End of Main را  چاپ می کند.

در نظر گرفتن یک sleep در این مثال فقط به منظور اطمینان از اجرای wait قبل از notify بوده و کاربرد دیگری ندارد.

خروجی تعامل چند Thread با یکدیگر
خروجی تعامل چند Thread با یکدیگر

مثال بالا را در حالت های مختلف مثلا کامنت کردن قسمت notify، اجرا و خروجی را مشاهده نمایید تا کاربرد این متدها را راحت تر درک کنید.

بررسی متد Interrupt

Thread ها با فراخوانی متدهای join یا sleep یا wait به حالت انتظار می روند و فعالت جاری را برای مدت زمانی متوقف می کنند، در این حالت اگر متد Interrupt بر روی شی این Thread فراخوانی شود می تواند منجر به پرتاب InterruptedException شود و به همین دلیل است که join و wait و sleep باید در بلوک Try-catch به کار برده شوند.

برای آشنایی بیشتر با این متد به مثال زیر توجه کنید:

public class Main {
    public static void main(String[] args) {
        Myinterrupt it = new Myinterrupt();
        it.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {}
        it.interrupt();
    }
}
class Myinterrupt extends Thread{
    @Override
    public synchronized void run() {
        try {
            this.wait();
            System.out.println("after Wait");
        } catch (InterruptedException ex) {
            System.out.println("this is interrupt");
        }
        System.out.println("End");
    }
}

در داخل کلاس Myinterrupt و در بدنه ی متد run، متد wait بر روی شی کلاس (this) فراخوانی شده و در نتیجه روند اجرای برنامه در اینجا متوقف می شود تا بر روی همین شی توسط Thread دیگری notify فراخوانی گردد و در نهایت عبارت after Wait چاپ شود.

در قسمت catch نیز دستوری برای اجرا نوشته شده است و در پایان هم End چاپ می شود.

در متد Main نیز Thread کلاس Myinterrupt  اجرا و بلافاصله Thread اصلی Main برای مدت زمان 1 ثانیه متوقف می شود، در مدت Thread کلاس Myinterrupt که در حال اجرا است بلافاصله در برخورد با wait متوقف می شود و منتظر می ماند تا شی مشترک notify شود تا بعد از آن به ترتیب عبارت after Wait و End چاپ شود و وظیفه اش به پایان برسد.

ولی این اتفاق نخواهد افتاد چون هیچ بلوکی برای notify کردن آن در متد Main توسط آن Thread در نظر گرفته نشده است.

در مدت زمانی که Thread کلاس Myinterrupt در حالت انتظار به سر می برد، Thread اصلی مدت زمان 1 ثانیه را سپری کرده و دستور it.interrupt() را اجرا می کند در نتیجه ی اجرای این دستور، در کلاس Myinterrupt نقطه ی اجرای برنامه قسمت try را رد کرده و دستورات داخل catch را اجرا می کند، پس after Wait هرگز اجرا نمی شود.

خروجی تعامل چند Thread با یکدیگر

پس کاربرد متد interrupt برای هدایت نقطه ی اجرای برنامه به بخش catch می باشد و بسته به نیاز برنامه نویس هر نوع دستوری را می توان در بخش catch قرار داد یا به بیان ساده تر فراخوانی متد interrupt بر روی یک Thread باعث دریافت خطای InterruptedException می شود .

پاسخ به یک سول مهم

سوال: تفاوت متدهای join و sleep و wait چیست؟

متد sleep برای مدت زمان مشخصی یک Thread را متوقف می کند و بعد از سپری شدن این مدت، Thread به فعالیت خود ادامه می دهد.

متد join نیز یک Thread را متوقف می کند تا یک Thread دیگر به پایان برسد.

با استفاده از wait یک Thread نیز متوقف می شود و منتظر می ماند تا توسط یک Thread دیگر با خبر شود (notify).

در قسمت بعدی آموزش مسأله ی Producer/Consumer که یک مثال عالی برای فهم مطالب گفته شده در این چند بخش است را بررسی می کنیم.

موفق باشید.

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

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