מדריך לשירות Java ExecutorService

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

שירות ExecutorService היא מסגרת המסופקת על ידי ה- JDK המפשטת את ביצוע המשימות במצב אסינכרוני. באופן כללי, שירות ExecutorService מספק באופן אוטומטי מאגר שרשורים ו- API להקצאת משימות לו.

2. מייצב שירות ExecutorService

2.1. שיטות המפעל של מוציאים לפועל מעמד

הדרך הקלה ביותר ליצור שירות ExecutorService היא להשתמש באחת משיטות המפעל של מוציאים לפועל מעמד.

לדוגמה, שורת הקוד הבאה תיצור מאגר אשכולות עם 10 אשכולות:

ExecutorService executor = Executors.newFixedThreadPool (10);

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

2.2. ישירות ליצור שירות ExecutorService

כי שירות ExecutorService הוא ממשק, ניתן להשתמש במופע של כל יישומיו. יש כמה יישומים לבחירה ב- java.util.concurrent חבילה או שאתה יכול ליצור משלך.

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

ExecutorService executorService = ThreadPoolExecutor חדש (1, 1, 0L, TimeUnit.MILLISECONDS, חדש LinkedBlockingQueue ());

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

3. הקצאת משימות ל שירות ExecutorService

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

Runnable runnableTask = () -> {נסה {TimeUnit.MILLISECONDS.sleep (300); } לתפוס (InterruptedException e) {e.printStackTrace (); }}; CallableTask = () -> הניתן להתקשרות (TimeUnit.MILLISECONDS.sleep (300); להחזיר "ביצוע המשימות"; }; רשימה callableTasks = ArrayList חדש (); callableTasks.add (callableTask); callableTasks.add (callableTask); callableTasks.add (callableTask);

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

ה לבצע() השיטה היא בָּטֵל, וזה לא נותן שום אפשרות להשיג את התוצאה של ביצוע המשימה או לבדוק את סטטוס המשימה (האם היא פועלת או מבוצעת).

executorService.execute (runnableTask);

שלח() מגיש א ניתן להתקשר או א ניתן לרוץ משימה ל- שירות ExecutorService ומחזיר תוצאה מהסוג עתיד.

עתיד עתידי = executorService.submit (callableTask);

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

תוצאת מחרוזת = executorService.invokeAny (משימות להתקשרות);

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

רשימה עתידיים = executorService.invokeAll (CallableTasks);

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

4. כיבוי שירות ExecutorService

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

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

מצד שני, אפליקציה יכולה להגיע לקצה שלה, אבל היא לא תיעצר בגלל המתנה שירות ExecutorService יגרום ל- JVM להמשיך לרוץ.

כדי לכבות כראוי שירות ExecutorService, יש לנו את ה לכבות() ו shutdownNow () ממשקי API.

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

executorService.shutdown ();

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

רשימת notExecutedTasks = executorService.shutDownNow ();

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

executorService.shutdown (); נסה {אם (! executorService.awaitTermination (800, TimeUnit.MILLISECONDS)) {executorService.shutdownNow (); }} לתפוס (InterruptedException e) {executorService.shutdownNow (); }

5. ה עתיד מִמְשָׁק

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

ה עתיד ממשק מספק שיטת חסימה מיוחדת לקבל() המחזירה תוצאה בפועל של ניתן להתקשר ביצוע המשימה או ריק במקרה של ניתן לרוץ מְשִׁימָה. קורא ל לקבל() השיטה בזמן שהמשימה עדיין פועלת תגרום לחסימת הביצוע עד שהמשימה תבוצע כראוי והתוצאה תהיה זמינה.

עתיד עתידי = executorService.submit (callableTask); תוצאת מחרוזת = null; נסה {result = future.get (); } לתפוס (InterruptedException | ExecutionException e) {e.printStackTrace (); }

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

תוצאת מחרוזת = future.get (200, TimeUnit.MILLISECONDS);

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

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

ה עתיד הממשק מספק גם ביטול ביצוע המשימות עם לְבַטֵל() שיטה, ולבדוק את הביטול באמצעות זה בוטל() שיטה:

בוליאני בוטל = future.cancel (נכון); isCancelled בוליאני = future.isCancelled ();

6. ה ScheduledExecutorService מִמְשָׁק

ה ScheduledExecutorService מריץ משימות לאחר עיכוב מוגדר מראש ו / או מעת לעת. שוב, הדרך הטובה ביותר לייצר א ScheduledExecutorService הוא להשתמש בשיטות המפעל של מוציאים לפועל מעמד.

לסעיף זה, א ScheduledExecutorService עם חוט אחד ישמש:

ScheduledExecutorService executorService = מוציאים לפועל .newSingleThreadScheduledExecutor ();

כדי לתזמן ביצוע של משימה אחת לאחר עיכוב קבוע, אנו מתוזמן () שיטת ה- ScheduledExecutorService. יש שני מתוזמן () שיטות שמאפשרות לך לבצע ניתן לרוץ אוֹ ניתן להתקשר משימות:

ResultFuture עתידי = executorService.schedule (CallableTask, 1, TimeUnit.SECONDS);

ה scheduleAtFixedRate () השיטה מאפשרת לבצע משימה מעת לעת לאחר עיכוב קבוע. הקוד שלמעלה מתעכב לשנייה אחת לפני ההפעלה callableTask.

גוש הקוד הבא יבצע משימה לאחר עיכוב ראשוני של 100 אלפיות השנייה, ולאחר מכן היא תבצע את אותה משימה כל 450 אלפיות השנייה. אם המעבד זקוק ליותר זמן לביצוע מטלה שהוקצתה מאשר ל- פרק זמן פרמטר של scheduleAtFixedRate () השיטה, ScheduledExecutorService ימתין עד שהמשימה הנוכחית תושלם לפני שתתחיל במשימה הבאה:

ResultFuture עתידי = service .scheduleAtFixedRate (runnableTask, 100, 450, TimeUnit.MILLISECONDS);

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

service.scheduleWithFixedDelay (משימה, 100, 150, TimeUnit.MILLISECONDS);

על פי scheduleAtFixedRate () ו schedulWithFixedDelay () חוזי שיטה, ביצוע תקופתי של המשימה יסתיים בסיום שירות ExecutorService או אם נזרק חריג במהלך ביצוע המשימות.

7. שירות ExecutorService מול מזלג / הצטרף

לאחר שחרורו של Java 7, מפתחים רבים החליטו כי ה- שירות ExecutorService יש להחליף את המסגרת במסגרת המזלג / הצטרפות. לא תמיד זו ההחלטה הנכונה. למרות פשטות השימוש ורווחי הביצועים התכופים הקשורים למזלג / הצטרפות, יש גם הפחתה בכמות השליטה במפתחים על ביצוע במקביל.

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

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

8. מסקנה

גם למרות הפשטות היחסית של שירות ExecutorService, יש כמה מלכודות נפוצות. בואו נסכם אותם:

שמירה על שימוש שאינו בשימוש שירות ExecutorService בחיים: יש הסבר מפורט בסעיף 4 במאמר זה על אופן סגירת ה- שירות ExecutorService;

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

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

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

הקוד למאמר זה זמין במאגר GitHub.


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