סקירה כללית של java.util.concurrent

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

ה java.util.concurrent החבילה מספקת כלים ליצירת יישומים בו זמנית.

במאמר זה נערוך סקירה כללית של כל החבילה.

2. רכיבים עיקריים

ה java.util.concurrent מכיל יותר מדי תכונות לדיון בכתיבה אחת. במאמר זה נתמקד בעיקר בכמה מהכלי עזר השימושיים ביותר מחבילה זו כמו:

  • מוציא להורג
  • שירות ExecutorService
  • ScheduledExecutorService
  • עתיד
  • CountDownLatch
  • CyclicBarrier
  • סֵמָפוֹר
  • ThreadFactory
  • BlockingQueue
  • DelayQueue
  • מנעולים
  • פאזר

תוכלו למצוא כאן מאמרים ייעודיים רבים לשיעורים בודדים.

2.1. מוציא להורג

מוציא להורג הוא ממשק המייצג אובייקט שמבצע משימות שסופקו.

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

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

עלינו ליצור מזמין ליצור את מופע ההוצאה לפועל:

Invoker בכיתה ציבורית מיישם את המוציא לפועל {@Override public void run (Runnable r) {r.run (); }}

כעת אנו יכולים להשתמש במזמין זה לביצוע המשימה.

ביצוע חלל ציבורי () {Executor executor = Invoker new (); executor.execute (() -> {// משימה לביצוע}); }

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

2.2. שירות ExecutorService

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

להשתמש שירות ExecutorService, אנחנו צריכים ליצור אחד כזה ניתן לרוץ מעמד.

מחלקה ציבורית משימות המשימה ניתנות להפעלה {@Override public void run () {// פרטי המשימה}}

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

ExecutorService executor = Executors.newFixedThreadPool (10);

אם אנחנו רוצים ליצור הברגה אחת שירות ExecutorService למשל, אנחנו יכולים להשתמש newSingleThreadExecutor (ThreadFactory threadFactory) כדי ליצור את המופע.

לאחר יצירת המוציא לפועל, נוכל להשתמש בה כדי להגיש את המשימה.

חלל ציבורי לבצע () {executor.submit (משימה חדשה ()); }

אנחנו יכולים גם ליצור את ניתן לרוץ למשל תוך הגשת המשימה.

executor.submit (() -> {משימה חדשה ();});

זה מגיע גם עם שתי שיטות סיום לביצוע מחוץ לקופסה. הראשון הוא לכבות(); זה ממתין עד לסיום הביצוע של כל המשימות שהוגשו. השיטה האחרת היא shutdownNow () מה?ח מסיים מיד את כל המשימות הממתינות / מבצעות.

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

נסה את {executor.awaitTermination (20l, TimeUnit.NANOSECONDS); } לתפוס (InterruptedException e) {e.printStackTrace (); }

2.3. ScheduledExecutorService

ScheduledExecutorService הוא ממשק דומה ל- שירות ExecutorService, אך הוא יכול לבצע משימות מעת לעת.

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

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

חלל ציבורי לבצע () {ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor (); עתיד עתידי = executorService.schedule (() -> {// ... להחזיר "שלום עולם";}, 1, TimeUnit.SECONDS); ScheduledFuture schedulFuture = executorService.schedule (() -> {// ...}, 1, TimeUnit.SECONDS); executorService.shutdown (); }

ScheduledExecutorService יכול גם לתזמן את המשימה לאחר עיכוב קבוע מסוים:

executorService.scheduleAtFixedRate (() -> {// ...}, 1, 10, TimeUnit.SECONDS); executorService.scheduleWithFixedDelay (() -> {// ...}, 1, 10, TimeUnit.SECONDS);

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

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

2.4. עתיד

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

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

אחרת, ניתן יהיה לבצע משימות מתבצעות.

אנו יכולים להשתמש בקטע הקוד למטה כדי ליצור מופע עתידי:

בטל פומבי להפעיל () {ExecutorService executorService = Executors.newFixedThreadPool (10); עתיד עתידי = executorService.submit (() -> {// ... Thread.sleep (10000l); להחזיר "שלום עולם";}); }

אנו יכולים להשתמש בקטע הקוד הבא כדי לבדוק אם התוצאה העתידית מוכנה ולהביא את הנתונים אם החישוב נעשה:

אם (future.isDone () &&! future.isCancelled ()) {נסה {str = future.get (); } לתפוס (InterruptedException | ExecutionException e) {e.printStackTrace (); }}

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

נסה את {future.get (10, TimeUnit.SECONDS); } לתפוס (InterruptedException | ExecutionException | TimeoutException e) {e.printStackTrace (); }

2.5. CountDownLatch

CountDownLatch (הוצג בשנת JDK 5) הוא מחלקת כלי עזר שחוסמת קבוצה של שרשורים עד להשלמת פעולה כלשהי.

א CountDownLatch מאותחל עם מונה (מספר שלם סוּג); מונה זה יורד כאשר החוטים התלויים משלימים את הביצוע. אבל ברגע שהדלפק מגיע לאפס, אשכולות אחרים משתחררים.

אתה יכול ללמוד עוד על CountDownLatch פה.

2.6. CyclicBarrier

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

עלינו ליצור ניתן לרוץ מופע משימה ליזום תנאי המחסום:

מעמד ציבורי מיישם משימות Runnable {מחסום CyclicBarrier פרטי; משימה ציבורית (מחסום מחזור) {this.barrier = מחסום; } @ עקוף הפעלה בטלנית ציבורית () {נסה {LOG.info (Thread.currentThread (). GetName () + "מחכה"); barrier.await (); LOG.info (Thread.currentThread (). GetName () + "משוחרר"); } לתפוס (InterruptedException | BrokenBarrierException e) {e.printStackTrace (); }}}

עכשיו אנחנו יכולים להפעיל כמה שרשורים כדי להתמודד על מצב המחסום:

התחלה בטלנית ציבורית () {CyclicBarrier cyclicBarrier = CyclicBarrier חדש (3, () -> {// ... LOG.info ("כל המשימות הקודמות הושלמו");}); חוט t1 = חוט חדש (משימה חדשה (מחזורית), "T1"); חוט t2 = חוט חדש (משימה חדשה (מחזורית), "T2"); חוט t3 = חוט חדש (משימה חדשה (מחזורית), "T3"); אם (! cyclicBarrier.isBroken ()) {t1.start (); t2.start (); t3.start (); }}

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

2.7. סֵמָפוֹר

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

אם אין היתר (דרך tryAcquire ()), אסור לשרשור לקפוץ לחלק הקריטי; עם זאת, אם ההיתר זמין הגישה ניתנת, ומונה ההיתר פוחת.

לאחר שרשור ההוצאה לפועל משחרר את החלק הקריטי, שוב מונה ההיתר עולה (נעשה על ידי לְשַׁחְרֵר() שיטה).

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

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

ניתן להשתמש בקטע הקוד הבא ליישום סמפור:

סמפור סמפורי סטטי = סמפור חדש (10); חלל ציבורי ביצוע () זורק InterruptedException {LOG.info ("היתר זמין:" + semaphore.availablePermits ()); LOG.info ("מספר האשכולות שמחכים לרכישה:" + semaphore.getQueueLength ()); אם (semaphore.tryAcquire ()) {נסה {// ...} סוף סוף {semaphore.release (); }}}

אנחנו יכולים ליישם א מנעול כמו שימוש במבנה נתונים סֵמָפוֹר. פרטים נוספים על כך ניתן למצוא כאן.

2.8. ThreadFactory

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

אנו יכולים להגדיר א ThreadFactory:

מחלקה ציבורית BaeldungThreadFactory מיישמת את ThreadFactory {private int threadId; שם מחרוזת פרטי; BaeldungThreadFactory ציבורי (שם מחרוזת) {threadId = 1; this.name = שם; } @ Override ציבורי חוט newThread (r ניתן להריצה) {Thread t = Thread new (r, name + "-Thread_" + threadId); LOG.info ("יצר חוט חדש עם מזהה:" + threadId + "ושם:" + t.getName ()); threadId ++; להחזיר t; }}

אנחנו יכולים להשתמש בזה newThread (runnable r) שיטה ליצירת שרשור חדש בזמן ריצה:

מפעל BaeldungThreadFactory = BaeldungThreadFactory חדש ("BaeldungThreadFactory"); עבור (int i = 0; i <10; i ++) {Thread t = factory.newThread (משימה חדשה ()); t.start (); }

2.9. BlockingQueue

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

מידע נוסף ודוגמא לעבודה בנושא זמינים כאן.

2.10. DelayQueue

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

מידע נוסף ודוגמא לעבודה בנושא זמינים כאן.

2.11. מנעולים

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

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

מידע נוסף ודוגמא לעבודה בנושא זמינים כאן.

2.12. פאזר

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

מידע נוסף ודוגמא לעבודה בנושא זמינים כאן.

3. מסקנה

במאמר סקירה ברמה גבוהה זו, התמקדנו בכל השירותים השונים שיש java.util.concurrent חֲבִילָה.

כמו תמיד, קוד המקור המלא זמין ב- GitHub.