Java CyclicBarrier לעומת CountDownLatch

1. הקדמה

במדריך זה נשווה CyclicBarrier ו CountDownLatch ולנסות להבין את הדמיון והשוני בין השניים.

2. מה אלה?

כשמדובר במקביליות, זה יכול להיות מאתגר להבין את מה שכל אחד מהם נועד להשיג.

בראש ובראשונה, שניהם CountDownLatch ו CyclicBarrier משמשים לניהול יישומים מרובי-הברגה.

וגם שניהם נועדו להביע כיצד חוט נתון או קבוצת חוטים צריכים לחכות.

2.1. CountDownLatch

א CountDownLatch הוא קונסטרוקט שהוא חוט לַחֲכוֹתהוא פועל בעוד שרשורים אחרים ספירה לאחור על התפס עד שהוא מגיע לאפס.

אנחנו יכולים לחשוב על זה כמו מנה במסעדה שמכינים אותה. לא משנה איזה טבח מכין כמה שיותר נ פריטים, המלצר חייב לַחֲכוֹת עד שכל הפריטים על הצלחת. אם צלחת לוקחת נ פריטים, כל טבח ירצה ספירה לאחור על התפס לכל פריט שהיא שמה על הצלחת.

2.2. CyclicBarrier

א CyclicBarrier הוא מבנה רב פעמי בו קבוצת חוטים מחכה יחד עד שכל החוטים לְהַגִיעַ. בנקודה זו, המחסום נשבר ו פעולה ניתן לקחת באופציה.

אנחנו יכולים לחשוב על זה כמו קבוצת חברים. בכל פעם שהם מתכננים לאכול במסעדה הם מחליטים נקודה משותפת בה הם יכולים להיפגש. הֵם לַחֲכוֹת זה לזה שם, ורק כשכולם מגיע האם הם יכולים ללכת למסעדה לאכול ביחד.

2.3. לקריאה נוספת

ולפרטים רבים יותר על כל אחד מהם בנפרד, עיין בהדרכות הקודמות שלנו בנושא CountDownLatch ו CyclicBarrier בהתאמה.

3. משימות מול חוטים

בואו נצלול עמוק יותר לכמה מההבדלים הסמנטיים בין שתי הכיתות הללו.

כאמור בהגדרות, CyclicBarrier מאפשר למספר שרשורים להמתין זה לזה, ואילו CountDownLatch מאפשר לשרשור אחד או יותר להמתין למספר משימות לביצוע.

בקצרה, CyclicBarrier שומר על ספירה של חוטים ואילו CountDownLatch שומר על ספירה של משימות.

בקוד הבא אנו מגדירים a CountDownLatch עם ספירה של שתיים. לאחר מכן, אנו מתקשרים ספירה לאחור() פעמיים מחוט בודד:

CountDownLatch countDownLatch = CountDownLatch חדש (2); שרשור t = שרשור חדש (() -> {countDownLatch.countDown (); countDownLatch.countDown ();}); t.start (); countDownLatch.await (); assertEquals (0, countDownLatch.getCount ());

ברגע שהתפס מגיע לאפס, השיחה אל לְהַמתִין החזרות.

שים לב שבמקרה זה, הצלחנו לגרום לאותו חוט להקטין את הספירה פעמיים.

CyclicBarrier, עם זאת, שונה בנקודה זו.

בדומה לדוגמא לעיל, אנו יוצרים a CyclicBarrier, שוב עם ספירה של שתיים והתקשר לְהַמתִין() עליו, הפעם מאותו שרשור:

CyclicBarrier cyclicBarrier = מחזור חדש (2); שרשור t = שרשור חדש (() -> {נסה {cyclicBarrier.await (); cyclicBarrier.await ();} לתפוס (InterruptedException | BrokenBarrierException e) {// טיפול בשגיאות}}); t.start (); assertEquals (1, cyclicBarrier.getNumberWaiting ()); assertFalse (cyclicBarrier.isBroken ());

ההבדל הראשון כאן הוא שהחוטים שמחכים הם עצמם המחסום.

שנית, וחשוב מכך, השני לְהַמתִין() הוא חסר תועלת. חוט אחד לא יכול ספירה לאחור מחסום פעמיים.

אכן בגלל t צריך לַחֲכוֹת לחוט אחר להתקשר לְהַמתִין() - להביא את הספירה לשניים - tהשיחה השנייה ל לְהַמתִין() לא יופעל בפועל עד שהמחסום כבר נשבר!

במבחן שלנו, המחסום לא נחצה כי יש לנו רק חוט אחד שמחכה ולא שני החוטים שיידרשו כדי להפסיק את המכשול. זה ניכר גם מה- cyclicBarrier.is שבור () שיטה, המחזירה שֶׁקֶר.

4. שימוש חוזר

ההבדל השני הבולט ביותר בין שתי המעמדות הללו הוא שימוש חוזר. לפרט, כאשר המכשול נכנס פנימה CyclicBarrier, הספירה מתאפסת לערכה המקורי.CountDownLatch שונה מכיוון שהספירה לעולם לא מתאפסת.

בקוד הנתון אנו מגדירים a CountDownLatch עם ספירה 7 וספר אותה באמצעות 20 שיחות שונות:

CountDownLatch countDownLatch = CountDownLatch חדש (7); ExecutorService es = Executors.newFixedThreadPool (20); עבור (int i = 0; i {long prevValue = countDownLatch.getCount (); countDownLatch.countDown (); if (countDownLatch.getCount ()! = prevValue) {outputScraper.add ("ספירה עודכנה");}}); } es.shutdown (); assertTrue (outputScraper.size () <= 7);

אנו צופים שלמרות ש -20 פתילים שונים מתקשרים ספירה לאחור()הספירה לא מתאפסת ברגע שהיא מגיעה לאפס.

בדומה לדוגמא לעיל, אנו מגדירים א CyclicBarrier עם ספירה 7 והמתינו עליה מ -20 אשכולות שונים:

CyclicBarrier cyclicBarrier = CyclicBarrier חדש (7); ExecutorService es = Executors.newFixedThreadPool (20); עבור (int i = 0; i {נסה {if (cyclicBarrier.getNumberWaiting () 7);

במקרה זה, אנו מבחינים כי הערך פוחת בכל פעם שרשור חדש פועל, על ידי איפוס לערך המקורי, ברגע שהוא מגיע לאפס.

5. מסקנה

הכל מהכל, CyclicBarrier ו CountDownLatchשניהם כלים מועילים לסנכרון בין מספר שרשורים. עם זאת, הם שונים במהותם מבחינת הפונקציונליות שהם מספקים. שקול היטב כל אחד מהם כאשר אתה קובע מה מתאים לתפקיד.

כרגיל, ניתן לגשת לכל הדוגמאות הנדונות ב- Github.