آشنایی مقدماتی با 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 را پیادهسازی میکند:
زمانی که کلاس اکشن ما وهلهسازی شد، با استفاده از متد ()register در اینترفیس Phaser ثبت نام میکنیم. به این ترتیب تعداد نخهایی که از آن Phaser خاص استفاده میکنند، افزایش مییابد.
فراخوانی به ()arriveAndAwaitAdvance موجب خواهد شد که نخ کنونی منتظر مانع بماند. همان طور که قبلاً اشاره کردیم، زمانی که تعداد طرفهای رسیده به مانع برابر با تعداد طرفهای ثبت نام کرده شود، اجرا تداوم خواهد یافت. پس از این که کار پردازش پایان یافت، نخ کنونی از طریق فراخوانی متد ()arriveAndDeregister خود را از ثبت نام خارج میکند.
در ادامه یک تست ایجاد میکنیم که سه نخ LongRunningAction را آغاز میکند و در مانع مسدود میشود. سپس بعد از آن که اکشن پایان گرفت، دو نخ LongRunningAction اضافی ایجاد میکنیم که پردازش فاز بعدی را انجام میدهند.
زمانی که وهله Phaser را از نخ اصلی ایجاد کردیم، عدد 1 را به عنوان آرگومان ارسال میکنیم. این عدد معادل فراخوانی متد ()register از نخ کنونی است. دلیل انجام این کار آن است که وقتی سه نخ کارگر ایجاد میشوند، نخ اصلی یک هماهنگکننده خواهد بود و از این رو Phaser به چهار نخ که در آن ثبت شده باشند، نیاز خواهد داشت:
از پس از مقداردهی اولیه برابر با صفر است.
کلاس Phaser یک سازنده دارد که میتوانیم وهله والد را به آنجا ارسال کنیم. این کار در مواردی مفید است که تعداد طرفهای مشارکتکننده زیاد باشد و هزینه هماهنگسازی سرسامآور شود. در چنین موقعیتهایی، وهلههای Phaser را میتوان طوری تنظیم کرد که گروههای Phaser فرعی دارای والد مشترکی باشند.
در ادامه سه نخ LongRunningAction را آغاز میکنیم که روی مانع صبر میکنند تا متد ()arriveAndAwaitAdvance را از نخ اصلی فراخوانی کنیم.
به خاطر داشته باشید که ما Phaser را با عدد 1 مقداردهی کردهایم و ()register را سه بار دیگر فراخواندهایم. اینک سه نخ اکشن اعلام کردهاند که به مانع رسیدهاند و از این رو به یک فراخوانی ()arriveAndAwaitAdvance دیگر نیاز داریم. این فراخوانی باید از سوی نخ اصلی انجام یابد:
پس از تکمیل شدن این فاز، متد ()getPhase عدد یک را بازگشت میدهد، زیرا برنامه پردازش نخستین گام اجرا را به پایان برده است.
حال فرض کنید دو نخ باید در فاز بعدی پردازش اجرا شوند. در این حالت میتوانیم از Phaser برای انجام این کار بهره بگیریم، زیرا به ما امکان میدهد که تعداد نخهایی که باید روی مانع منتظر بمانند را به صورت دینامیک پیکربندی کنیم. کار خود را با دو نخ آغاز میکنیم، اما این نخها اجرا نمیشوند، مگر این که فراخوانی ()arriveAndAwaitAdvance از سوی نخ اصلی بیاید:
در ادامه متد ()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 است و از این رو میتوانید آن را در پروژههای خود ایمپورت و اجرا کنید.