כיצד לעצור ביצוע לאחר זמן מסוים בג'אווה

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

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

2. שימוש בלולאה

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

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

בואו נראה דוגמה מהירה:

התחלה ארוכה = System.currentTimeMillis (); סוף ארוך = התחלה + 30 * 1000; בעוד (System.currentTimeMillis () <end) {// פעולה יקרה כלשהי על הפריט. }

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

  • דיוק נמוך: הלולאה יכולה לרוץ יותר ממגבלת הזמן שהוטלה. זה יהיה תלוי בזמן שכל איטרציה עשויה להימשך. לדוגמא, אם כל איטרציה עשויה לארוך עד 7 שניות, אז הזמן הכולל יכול להגיע עד 35 שניות, וזה ארוך בערך 17% ממגבלת הזמן הרצויה של 30 שניות.
  • חסימה: עיבוד כזה בשרשור הראשי לא יכול להיות רעיון טוב מכיוון שהוא יחסום אותו לאורך זמן. במקום זאת, יש לנתק פעולות אלה מהחוט הראשי

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

3. שימוש במנגנון הפסקה

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

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

בואו נסתכל על חוט העובד:

class LongRunningTask מיישם Runnable {@Override public void run () {try {while (! Thread.interrupted ()) {Thread.sleep (500); }} לתפוס (InterruptedException e) {// שגיאת יומן}}}

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

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

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

3.1. באמצעות א שָׁעוֹן עֶצֶר

לחלופין, אנו יכולים ליצור TimerTask להפריע לחוט העובד בזמן פסק הזמן:

מחלקה TimeOutTask מרחיב את TimerTask {חוט פרטי t; טיימר טיימר פרטי; TimeOutTask (חוט t, טיימר טיימר) {this.t = t; this.timer = טיימר; } הפעלה בטלנית ציבורית () {if (t! = null && t.isAlive ()) {t.interrupt (); timer.cancel (); }}}

הנה, הגדרנו א TimerTask שלוקח חוט עובד בזמן היווצרותו. זה יהיה קוטע את חוט העובד עם קריאתו לָרוּץ שיטה. ה שָׁעוֹן עֶצֶר יפעיל את TimerTask לאחר העיכוב שצוין:

שרשור t = שרשור חדש (LongRunningTask חדש ()); טיימר טיימר = טיימר חדש (); timer.schedule (TimeOutTask חדש (t, טיימר), 30 * 1000); t.start ();

3.2. באמצעות השיטה עתיד # קבל

אנחנו יכולים גם להשתמש ב- לקבל שיטת א עתיד במקום להשתמש ב- שָׁעוֹן עֶצֶר:

ExecutorService executor = Executors.newSingleThreadExecutor (); עתיד עתידי = executor.submit (LongRunningTask חדש ()); נסה את {f.get (30, TimeUnit.SECONDS); } לתפוס (TimeoutException e) {f.cancel (true); } סוף סוף {service.shutdownNow (); }

הנה, השתמשנו ב- שירות ExecutorService להגיש את שרשור העובד המחזיר מופע של עתיד, של מי לקבל השיטה תחסום את השרשור הראשי עד לשעה שצוינה. זה יעלה a פסק זמן פסק זמן לאחר פסק הזמן שצוין. בתוך ה לתפוס לחסום, אנו קוטעים את חוט העובד על ידי קריאת ה- לְבַטֵל שיטה על Fמטבע לְהִתְנַגֵד.

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

3.3. באמצעות א מתוזמן ExcecutorSercvice

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

ScheduledExecutorService executor = Executors.newScheduledThreadPool (2); עתיד עתידי = executor.submit (LongRunningTask חדש ()); executor.schedule (חדש Runnable () {public void run () {future.cancel (true);}}, 1000, TimeUnit.MILLISECONDS); executor.shutdown ();

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

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

שים לב שבניגוד לגישה הקודמת, איננו חוסמים את החוט המרכזי על ידי קריאה ל- עתיד # קבל שיטה. לָכֵן, זו הגישה המועדפת ביותר מבין כל הגישות הנ"ל.

4. האם יש אחריות?

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

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

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

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

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

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

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

5. מסקנה

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