מחזור חיים של חוט בג'אווה

1. הקדמה

במאמר זה נדון בפירוט במושג ליבה בג'אווה - מחזור החיים של שרשור.

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

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

2. Multithreading בג'אווה

בשפת Java, ריבוי הליכי משנה מונע על ידי מושג הליבה של Thread. במהלך מחזור החיים שלהם, חוטים עוברים מצבים שונים:

3. מחזור חיים של חוט בג'אווה

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

  1. חדש - שרשור חדש שעדיין לא התחיל את הביצוע
  2. לרוץ - או פועל או מוכן לביצוע אך הוא ממתין להקצאת משאבים
  3. חסום - מחכה לרכישת מנעול צג כדי להיכנס או להזין מחדש בלוק / שיטה מסונכרנת
  4. מחכה - מחכה לאיזה חוט אחר שיבצע פעולה מסוימת ללא הגבלת זמן
  5. TIMED_WAITING - מחכה לאיזה שרשור אחר יבצע פעולה ספציפית לתקופה מוגדרת
  6. הופסק - סיים את ביצועו

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

3.1. חָדָשׁ

א חָדָשׁפְּתִיל (או נולד פְּתִיל) הוא שרשור שנוצר אך טרם התחיל. זה נשאר במצב זה עד שנתחיל להשתמש בו באמצעות ה- הַתחָלָה() שיטה.

קטע הקוד הבא מציג שרשור חדש שנוצר ב חָדָשׁ מדינה:

Runnable runnable = NewState חדש (); חוט t = חוט חדש (ניתן להריצה); Log.info (t.getState ());

מכיוון שלא התחלנו את השרשור שהוזכר, השיטה t.getState () הדפסים:

חָדָשׁ

3.2. ניתן לרוץ

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

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

לדוגמא, בואו נוסיף t.start () שיטה לקוד הקודם שלנו ונסה לגשת למצב הנוכחי:

Runnable runnable = NewState חדש (); חוט t = חוט חדש (ניתן להריצה); t.start (); Log.info (t.getState ());

קוד זה הוא ככל הנראה להחזיר את הפלט כ:

לרוץ

שים לב שבדוגמה זו, לא תמיד מובטח שעד שהשליטה שלנו תגיע t.getState (), זה עדיין יהיה ב לרוץ מדינה.

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

3.3. חָסוּם

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

בואו ננסה לשחזר את המצב הזה:

class public BlockedState {public static void main (String [] args) זורק InterruptedException {Thread t1 = Thread new (DemoThreadB new) ()); חוט t2 = חוט חדש (חדש DemoThreadB ()); t1.start (); t2.start (); Thread.sleep (1000); Log.info (t2.getState ()); System.exit (0); }} DemoThreadB בכיתה מיישמת Runnable {@Override public void run () {commonResource (); } ציבורי סטטי מסונכרן ריק ריק CommonResource () {תוך (נכון) {// לולאה אינסופית לחיקוי עיבוד כבד // 't1' לא יעזוב את השיטה הזו // כאשר 't2' תנסה להזין את זה}}}

בקוד זה:

  1. יצרנו שני שרשורים שונים - t1 ו t2
  2. t1 מתחיל ונכנס לסנכרון commonResource () שיטה; פירוש הדבר שרק חוט אחד יכול לגשת אליו; כל שאר האשכולות הבאים המנסים לגשת לשיטה זו ייחסמו מהביצוע נוסף עד אשר הנוכחי יסיים את העיבוד
  3. מתי t1 נכנס לשיטה זו, היא נשמרת באינסוף תוך לולאה; זה רק כדי לחקות עיבוד כבד כדי שכל שאר האשכולות לא יוכלו להיכנס לשיטה זו
  4. עכשיו כשאנחנו מתחילים t2, הוא מנסה להיכנס ל- commonResource () השיטה, אליה כבר ניגשת t1, לכן, t2 יישמר ב חָסוּם מדינה

בהיותנו במצב זה, אנו קוראים t2.getState () וקבל את התפוקה כ:

חָסוּם

3.4. הַמתָנָה

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

  1. object.wait ()
  2. thread.join () אוֹ
  3. LockSupport.park ()

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

יש לנו הדרכה נפרדת הדנה בפירוט בשימוש ב לַחֲכוֹת(), לְהוֹדִיעַ() ו notifyAll ().

לעת עתה, ננסה לשחזר את המצב הזה:

מחלקה ציבורית WaitingState מיישמת Runnable {חוט סטטי ציבורי t1; סטטי ציבורי ריק ריק (מחרוזת [] טענות) {t1 = שרשור חדש (WaitingState חדש ()); t1.start (); } הפעלה בטלנית ציבורית () {חוט t2 = חוט חדש (חדש DemoThreadWS ()); t2.start (); נסה את {t2.join (); } לתפוס (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("החוט הופרע", ה); }}} DemoThreadWS בכיתה מיישמת Runnable {public void run () {try {Thread.sleep (1000); } לתפוס (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("החוט הופרע", ה); } Log.info (WaitingState.t1.getState ()); }}

בואו נדון במה שאנחנו עושים כאן:

  1. יצרנו והתחלנו את t1
  2. t1 יוצר a t2 ומתחיל את זה
  3. תוך כדי העיבוד של t2 ממשיך, אנחנו קוראים t2.join (), זה מציב t1 ב הַמתָנָה ציין עד t2 סיים את הביצוע
  4. מאז t1 מחכה ל t2 כדי להשלים, אנחנו מתקשרים t1.getState () מ t2

התפוקה כאן היא, כפי שהיית מצפה:

הַמתָנָה

3.5. המתנה מתוזמנת

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

על פי JavaDocs, יש חמש דרכים לשים שרשור TIMED_WAITING מדינה:

  1. thread.sleep (מיליס ארוך)
  2. המתן (פסק זמן int) אוֹ המתן (פסק זמן int, int ננו)
  3. thread.join (ארוך מיליס)
  4. LockSupport.parkNanos
  5. LockSupport.parkUntil

לקריאה נוספת על ההבדלים בין לַחֲכוֹת() ו לִישׁוֹן() ב- Java, עיין במאמר ייעודי זה כאן.

לעת עתה, בוא ננסה לשחזר מצב זה במהירות:

מחלקה ציבורית TimedWaitingState {public static void main (String [] args) זורק InterruptedException {DemoThread obj1 = DemoThread חדש (); חוט t1 = חוט חדש (obj1); t1.start (); // השינה הבאה תיתן מספיק זמן ל- ThreadScheduler // להתחיל לעבד את החוט t1 Thread.sleep (1000); Log.info (t1.getState ()); }} DemoThread בכיתה מיישם Runnable {@Override public void run () {try {Thread.sleep (5000); } לתפוס (InterruptedException e) {Thread.currentThread (). interrupt (); Log.error ("החוט הופרע", ה); }}}

הנה, יצרנו והתחלנו שרשור t1 אשר נכנס למצב שינה עם פרק זמן קצוב של 5 שניות; התפוקה תהיה:

TIMED_WAITING

3.6. הופסק

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

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

בואו ננסה להשיג מצב זה בדוגמה הבאה:

מחלקה ציבורית TerminatedState מיישמת Runnable {public static void main (String [] args) throw InterruptedException {Thread t1 = Thread new (New TerminatedState ()); t1.start (); // שיטת השינה הבאה תיתן מספיק זמן ל // thread t1 להשלמת Thread.sleep (1000); Log.info (t1.getState ()); } @ עקוף ריצת חלל ציבורית () {// אין עיבוד בבלוק זה}}

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

הופסק

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

Assert.assertFalse (t1.isAlive ());

זה חוזר שֶׁקֶר. במילים פשוטות, חוט חי אם ורק אם יש לו התחיל וטרם מת.

4. מסקנה

במדריך זה למדנו על מחזור החיים של שרשור ב- Java. בדקנו את כל שש המדינות שהוגדרו על ידי חוט. מדינה enum והעתק אותם עם דוגמאות מהירות.

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

וכמו תמיד, קטעי הקוד המשמשים כאן זמינים ב- GitHub.