מדריך java.util.concurrent.BlockingQueue

1. סקירה כללית

במאמר זה נבחן את אחד המבנים השימושיים ביותר java.util.concurrent כדי לפתור את בעיית היצרן-הצרכן במקביל. נבחן API של ה- BlockingQueue ממשק וכיצד שיטות מממשק זה מקלות על כתיבת תוכניות במקביל.

בהמשך המאמר נציג דוגמה לתוכנית פשוטה הכוללת מספר אשכולות מפיקים ומספר נושאים צרכניים.

2. BlockingQueue סוגים

אנו יכולים להבחין בין שני סוגים של BlockingQueue:

  • תור ללא גבולות - יכול לגדול כמעט ללא הגבלת זמן
  • תור מוגבל - עם מוגדר קיבולת מקסימאלית

2.1. תור ללא גבולות

יצירת תורים ללא גבולות היא פשוטה:

BlockingQueue blockingQueue = חדש LinkedBlockingDeque ();

היכולת של blockingQueue יוגדר ל מספר שלם.MAX_VALUE. כל הפעולות שמוסיפות אלמנט לתור הבלתי מוגבל לעולם לא יחסמו, ולכן הוא יכול לגדול לגודל גדול מאוד.

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

2.2. תור מוגבל

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

BlockingQueue blockingQueue = חדש LinkedBlockingDeque (10);

הנה יש לנו blockingQueue שיש לו קיבולת השווה ל 10. זה אומר שכאשר מפיק מנסה להוסיף אלמנט לתור כבר מלא, תלוי בשיטה בה השתמשו להוסיף אותו (הַצָעָה(), לְהוֹסִיף() אוֹ לָשִׂים()), זה ייחסם עד שיהיה מקום להכנסת אובייקט. אחרת, הפעולות ייכשלו.

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

3. BlockingQueue ממשק API

ישנם שני סוגים של שיטות BlockingQueue מִמְשָׁקשיטות האחראיות על הוספת אלמנטים לתור ושיטות המאחזרות את אותם אלמנטים. כל שיטה משתי הקבוצות האלה מתנהגת בצורה שונה במקרה שהתור מלא / ריק.

3.1. הוספת אלמנטים

  • הוסף () - החזרות נָכוֹן אם ההכנסה הצליחה, אחרת זורק IllegalStateException
  • לשים () - מכניס את האלמנט שצוין לתור, ומחכה לחריץ פנוי במידת הצורך
  • הצעה () - החזרות נָכוֹן אם ההכנסה הצליחה, אחרת שֶׁקֶר
  • הצעה (E e, פסק זמן ארוך, יחידת TimeUnit) - מנסה להכניס אלמנט לתור ומחכה לחריץ זמין בתוך זמן קצוב שנקבע

3.2. אחזור אלמנטים

  • לקחת() - ממתין לאלמנט ראש בתור ומסיר אותו. אם התור ריק, הוא חוסם ומחכה שרכיב יהיה זמין
  • סקר (פסק זמן ארוך, יחידת TimeUnit) - מאחזר ומסיר את ראש התור, ממתין עד זמן ההמתנה שצוין במידת הצורך כדי שרכיב יהיה זמין. החזרות ריק לאחר פסק זמן

שיטות אלה הן אבני הבניין החשובות ביותר מ BlockingQueue ממשק בעת בניית תוכניות יצרנים-צרכנים.

4. דוגמה ליצרן-צרכני מרובי-הברגה

בואו ניצור תוכנית המורכבת משני חלקים - מפיק וצרכן.

המפיק יפיק מספר אקראי בין 0 ל 100 ויכניס את המספר לספרה BlockingQueue. יהיו לנו 4 אשכולות מפיקים ונשתמש ב לָשִׂים() שיטה לחסום עד שיהיה מקום פנוי בתור.

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

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

בואו נסתכל על כיתת מפיקים:

מחלקה ציבורית NumbersProducer מיישמת Runnable {private BlockingQueue numbersQueue; רעל גליל פרטי פרטי; פרטית int poisonPillPerProducer הסופי; NumbersProducer ציבוריים (BlockingQueue numbersQueue, int poisonPill, int poisonPillPerProducer) {this.numbersQueue = numbersQueue; this.poisonPill = poisonPill; this.poisonPillPerProducer = poisonPillPerProducer; } הפעלה בטלנית ציבורית () {נסה {createNumbers (); } לתפוס (InterruptedException e) {Thread.currentThread (). interrupt (); }} ריק ריק generatingNumbers () זורק את InterruptedException {עבור (int i = 0; i <100; i ++) {numbersQueue.put (ThreadLocalRandom.current (). nextInt (100)); } עבור (int j = 0; j <poisonPillPerProducer; j ++) {numbersQueue.put (poisonPill); }}}

בונה המפיק שלנו לוקח כטיעון את BlockingQueue המשמש לתיאום עיבוד בין המפיק לצרכן. אנו רואים את השיטה הזו createNumbers () יכניס 100 אלמנטים לתור. נדרש גם הודעת גלולת רעל כדי לדעת איזה סוג של הודעה יש להכניס לתור כאשר הביצוע יסתיים. צריך לשים את המסר הזה poisonPillPerProducer פעמים בתור.

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

זה ייתן לנו תובנה לגבי הפעולות הפנימיות של הצרכנים שלנו:

מחלקה ציבורית NumbersConsumer מיישמת Runnable {private BlockingQue queue; רעל גליל פרטי פרטי; NumbersConsumer ציבוריים (תור BlockingQueue, int poisonPill) {this.queue = תור; this.poisonPill = poisonPill; } הפעלה בטלנית ציבורית () {נסה {בעוד (נכון) {מספר שלם = תור.קח (); אם (number.equals (poisonPill)) {return; } System.out.println (Thread.currentThread (). GetName () + "תוצאה:" + מספר); }} לתפוס (InterruptedException e) {Thread.currentThread (). interrupt (); }}}

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

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

אנו רוצים שיהיו לנו 4 שרשורי מפיקים ומספר שרשורי צרכנים יהיו שווים למספר המעבדים הזמינים:

int BOUND = 10; int N_PRODUCERS = 4; int N_CONSUMERS = Runtime.getRuntime (). availableProcessors (); int poisonPill = Integer.MAX_VALUE; int poisonPillPerProducer = N_CONSUMERS / N_PRODUCERS; int mod = N_CONSUMERS% N_PRODUCERS; תור BlockingQueue = חדש LinkedBlockingQueue (BOUND); עבור (int i = 1; i <N_PRODUCERS; i ++) {שרשור חדש (NumbersProducer חדש (תור, poisonPill, poisonPillPerProducer)). התחל (); } עבור (int j = 0; j <N_CONSUMERS; j ++) {שרשור חדש (NumbersConsumer חדש (תור, poisonPill)). התחל (); } שרשור חדש (NumbersProducer חדש (תור, poisonPill, poisonPillPerProducer + mod)). התחל (); 

BlockingQueue נוצר באמצעות קונסטרוקט עם יכולת. אנו יוצרים 4 יצרנים וצרכנים N. אנו מציינים את הודעת גלולת הרעל שלנו כ- מספר שלם.MAX_VALUE מכיוון שמפיק כזה לעולם לא ישלח על ידי המפיק שלנו בתנאי עבודה רגילים. הדבר החשוב ביותר שיש לשים לב כאן הוא זה BlockingQueue משמש לתיאום העבודה ביניהם.

כשאנחנו מפעילים את התוכנית, 4 שרשורי מפיקים יוצרים אקראיים שלמים ב BlockingQueue והצרכנים ייקחו את האלמנטים האלה מהתור. כל שרשור ידפיס לפלט רגיל את שם החוט יחד עם תוצאה.

5. מסקנה

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

ניתן למצוא את היישום של כל הדוגמאות וקטעי הקוד בפרויקט GitHub - זהו פרויקט מבוסס Maven, כך שיהיה קל לייבא ולהפעיל אותו כפי שהוא.


$config[zx-auto] not found$config[zx-overlay] not found