آشنایی مقدماتی با Java Phaser — به زبان ساده

۸۱ بازدید
آخرین به‌روزرسانی: ۲۰ شهریور ۱۴۰۲
زمان مطالعه: ۴ دقیقه
آشنایی مقدماتی با Java Phaser — به زبان ساده

در این مقاله به بررسی سازنده Phaser (+) از پکیج java.util.concurrent خواهیم پرداخت. این سازنده شباهت زیادی به سازنده CountDownLatch دارد که به ما امکان می‌دهد اجرای نخ‌ها را هماهنگ کنیم. با این حال Java Phaser در قیاس با CountDownLatch کارکردهای بیشتری دارد.

Phaser یک مانع در برابر تعداد دینامیک نخ‌هایی است که باید پیش از ادامه اجرا منتظر بمانند. در CountDownLatch که این تعداد نمی‌توانند به صورت دینامیک پیکربندی شوند، باید در زمان ایجاد وهله ارائه شوند.

Phaser API

Phaser به ما امکان می‌دهد که یک منطق بسازیم که در آن نخ‌ها باید در مانع منتظر بمانند تا بتوانند وارد گام بعدی اجرا شوند.

به این ترتیب می‌توانیم چند فاز اجرا را مدیریت کنیم و از وهله Phaser مجدداً برای هر فاز برنامه استفاده کنیم. هر فاز می‌تواند چند عدد نخ داشته باشد که منتظر پیشروی به فاز بعدی هستند. در ادامه یک مثال در مورد استفاده از فازها را برسی خواهیم کرد.

نخ برای مشارکت در هماهنگی باید خود را در وهله Phaser با استفاده از متد ()register ثبت کند. توجه کنید که این کار تنها تعداد طرف‌های ثبت شده را افزایش می‌دهد و نمی‌توانیم بررسی کنیم آیا نخ کنونی ثبت شده است یا نه. برای پشتیبانی از این امکان باید یک زیرکلاس از پیاده‌سازی تهیه کنیم.

نخ با فراخوانی ()arriveAndAwaitAdvance که یک متد مسدودساز است، علامت می‌دهد که به مانع رسیده است. زمانی که تعداد طرف‌های رسیده به مانع برابر با تعداد طرف‌های ثبت شده شود، اجرای برنامه ادامه خواهد یافت و عدد فاز افزایش می‌یابد. به این ترتیب می‌توانیم عدد فاز کنونی را با فراخوانی متد ()getPhase به دست آوریم.

زمانی که نخ کار خود را پایان دهد، باید متد ()arriveAndDeregister را فراخوانی کنیم تا مشخص شود که نخ کنونی دیگر نباید مسئول این فاز خاص باشد.

پیاده‌سازی منطق با استفاده از Phaser API

فرض کنید که می‌خواهیم چند فاز اکشن‌ها را با یکدیگر هماهنگ کنیم. سه نخ فاز نخست را پردازش می‌کند و دو نخ دیگر نیز به پردازش فاز دوم می‌پردازند. در ادامه کلاس LongRunningAction را ایجاد می‌کنیم که اینترفیس Runnable را پیاده‌سازی می‌کند:

1class LongRunningAction implements Runnable {
2    private String threadName;
3    private Phaser ph;
4 
5    LongRunningAction(String threadName, Phaser ph) {
6        this.threadName = threadName;
7        this.ph = ph;
8        ph.register();
9    }
10 
11    @Override
12    public void run() {
13        ph.arriveAndAwaitAdvance();
14        try {
15            Thread.sleep(20);
16        } catch (InterruptedException e) {
17            e.printStackTrace();
18        }
19        ph.arriveAndDeregister();
20    }
21}

زمانی که کلاس اکشن ما وهله‌سازی شد، با استفاده از متد ()register در اینترفیس Phaser ثبت نام می‌کنیم. به این ترتیب تعداد نخ‌هایی که از آن Phaser خاص استفاده می‌کنند، افزایش می‌یابد.

فراخوانی به ()arriveAndAwaitAdvance موجب خواهد شد که نخ کنونی منتظر مانع بماند. همان طور که قبلاً اشاره کردیم، زمانی که تعداد طرف‌های رسیده به مانع برابر با تعداد طرف‌های ثبت نام کرده شود، اجرا تداوم خواهد یافت. پس از این که کار پردازش پایان یافت، نخ کنونی از طریق فراخوانی متد ()arriveAndDeregister خود را از ثبت نام خارج می‌کند.

در ادامه یک تست ایجاد می‌کنیم که سه نخ LongRunningAction را آغاز می‌کند و در مانع مسدود می‌شود. سپس بعد از آن که اکشن پایان گرفت، دو نخ LongRunningAction اضافی ایجاد می‌کنیم که پردازش فاز بعدی را انجام می‌دهند.

زمانی که وهله Phaser را از نخ اصلی ایجاد کردیم، عدد 1 را به عنوان آرگومان ارسال می‌کنیم. این عدد معادل فراخوانی متد ()register از نخ کنونی است. دلیل انجام این کار آن است که وقتی سه نخ کارگر ایجاد می‌شوند، نخ اصلی یک هماهنگ‌کننده خواهد بود و از این رو Phaser به چهار نخ که در آن ثبت شده باشند، نیاز خواهد داشت:

1ExecutorService executorService = Executors.newCachedThreadPool();
2Phaser ph = new Phaser(1);
3 
4assertEquals(0, ph.getPhase());

از پس از مقداردهی اولیه برابر با صفر است.

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

Java Phaser

در ادامه سه نخ LongRunningAction را آغاز می‌کنیم که روی مانع صبر می‌کنند تا متد ()arriveAndAwaitAdvance را از نخ اصلی فراخوانی کنیم.

به خاطر داشته باشید که ما Phaser را با عدد 1 مقداردهی کرده‌ایم و ()register را سه بار دیگر فراخوانده‌ایم. اینک سه نخ اکشن اعلام کرده‌اند که به مانع رسیده‌اند و از این رو به یک فراخوانی ()arriveAndAwaitAdvance دیگر نیاز داریم. این فراخوانی باید از سوی نخ اصلی انجام یابد:

1executorService.submit(new LongRunningAction("thread-1", ph));
2executorService.submit(new LongRunningAction("thread-2", ph));
3executorService.submit(new LongRunningAction("thread-3", ph));
4 
5ph.arriveAndAwaitAdvance();
6 
7assertEquals(1, ph.getPhase());

پس از تکمیل شدن این فاز، متد ()getPhase عدد یک را بازگشت می‌دهد، زیرا برنامه پردازش نخستین گام اجرا را به پایان برده است.

حال فرض کنید دو نخ باید در فاز بعدی پردازش اجرا شوند. در این حالت می‌توانیم از Phaser برای انجام این کار بهره بگیریم، زیرا به ما امکان می‌دهد که تعداد نخ‌هایی که باید روی مانع منتظر بمانند را به صورت دینامیک پیکربندی کنیم. کار خود را با دو نخ آغاز می‌کنیم، اما این نخ‌ها اجرا نمی‌شوند، مگر این که فراخوانی ()arriveAndAwaitAdvance از سوی نخ اصلی بیاید:

1executorService.submit(new LongRunningAction("thread-4", ph));
2executorService.submit(new LongRunningAction("thread-5", ph));
3ph.arriveAndAwaitAdvance();
4 
5assertEquals(2, ph.getPhase());
6 
7ph.arriveAndDeregister();

در ادامه متد ()getPhase عدد فاز را به صورت 2 بازگشت می‌دهد. زمانی که بخواهیم برنامه را خاتمه بدهیم، باید متد ()arriveAndDeregiste را فراخوانی کنیم، چون نخ اصلی همچنان در ()arriveAndDeregister ثبت است. زمانی که این خروج از ثبت موجب شود که عدد طرف‌های ثبت شده به صفر کاهش یابد، Phaser خاتمه خواهد یافت. در این زمان همه فراخوانی‌ها به متدهای همگام‌سازی دیگر مسدود نخواهند ساخت و بی‌درنگ بازگشت می‌یابند.

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

This is phase 0
This is phase 0
This is phase 0
Thread thread-2 before long running action
Thread thread-1 before long running action
Thread thread-3 before long running action
This is phase 1
This is phase 1
Thread thread-4 before long running action
Thread thread-5 before long running action

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

سخن پایانی

در این مقاله با سازنده Phaser از پکیج java.util.concurrent آشنا شدیم و منطق هماهنگ‌سازی را با استفاده از چند فاز با استفاده از کلاس Phaser پیاده‌سازی کردیم.

پیاده‌سازی همه این مثال‌ها و قطعات کد را می‌توانید در این ریپوی گیت‌هاب (+) مشاهده کنید. همچنین این یک پروژه Maven است و از این رو می‌توانید آن را در پروژه‌های خود ایمپورت و اجرا کنید.

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
baeldung
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *