ג 'אווה חוט סתום ו Livelock

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

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

2. מבוי סתום

2.1. מהו מבוי סתום?

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

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

2.2. דוגמה למבוי סתום

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

בדוגמה זו ניצור שני שרשורים, T1 ו T2. פְּתִיל T1 שיחות פעולה 1, וחוט T2 שיחות פעולות.

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

עכשיו, בוא נכתוב את דוגמה למבוי סתום מעמד:

כיתה ציבורית Deadlock דוגמה {נעילה פרטית נעילה 1 = ReentrantLock חדש (נכון); נעילת נעילה פרטית 2 = ReentrantLock חדש (נכון); ריק סטטי ציבורי ראשי (String [] args) {DeadlockExample deadlock = DeadlockExample new (); שרשור חדש (מבוי סתום :: פעולה 1, "T1"). התחל (); שרשור חדש (מבוי סתום :: פעולה 2, "T2"). התחל (); } פעולת חלל ציבורית 1 () {lock1.lock (); הדפס ("נעילה 1 נרכשת, מחכה לרכישת נעילה 2."); שינה (50); lock2.lock (); הדפס ("lock2 נרכש"); הדפס ("ביצוע פעולה ראשונה."); lock2.unlock (); lock1.unlock (); } פעולת חלל ציבורית 2 () {lock2.lock (); הדפס ("lock2 נרכש, מחכה לרכישת lock1."); שינה (50); lock1.lock (); הדפס ("lock1 נרכש"); הדפס ("ביצוע פעולה שניה."); lock1.unlock (); lock2.unlock (); } // שיטות עוזר}

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

חוט T1: נעילה 1 נרכשת, מחכה לרכישת נעילה 2. חוט T2: נעילה 2 נרכשת, מחכה לרכישת נעילה 1.

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

2.3. הימנעות מבוי סתום

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

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

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

3. Livelock

3.1. מה זה Livelock

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

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

3.2. דוגמה של Livelock

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

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

בואו נדגים בעלי חיים עם א דוגמה של Livelock מעמד:

מחלקה ציבורית Livelock דוגמא {lock lock lock1 = ReentrantLock חדש (נכון); נעילת נעילה פרטית 2 = ReentrantLock חדש (נכון); main static public ריק (String [] args) {LivelockExample livelock = LivelockExample חדש (); שרשור חדש (livelock :: operation1, "T1"). start (); שרשור חדש (livelock :: operation2, "T2"). start (); } פעולת חלל ציבורית 1 () {while (true) {tryLock (lock1, 50); הדפס ("נעילה 1 נרכשת, מנסה להשיג נעילה 2."); שינה (50); אם (tryLock (lock2)) {print ("lock2 נרכש."); } אחר {print ("לא יכול לרכוש lock2, לשחרר lock1."); lock1.unlock (); לְהַמשִׁיך; } הדפס ("ביצוע פעולה ראשונה."); לשבור; } lock2.unlock (); lock1.unlock (); } פעולת חלל ציבורית 2 () {while (true) {tryLock (lock2, 50); הדפס ("lock2 נרכש, מנסה לרכוש lock1."); שינה (50); אם (tryLock (lock1)) {print ("lock1 נרכש."); } אחר {print ("לא יכול לרכוש lock1, לשחרר lock2."); lock2.unlock (); לְהַמשִׁיך; } הדפס ("ביצוע פעולה שנייה."); לשבור; } lock1.unlock (); lock2.unlock (); } // שיטות עוזר}

עכשיו בואו נפעיל את הדוגמה הזו:

חוט T1: נעילה 1 נרכשת, מנסה לרכוש נעילה 2. חוט T2: נעילה 2 נרכשת, מנסה להשיג נעילה 1. חוט T1: לא יכול להשיג נעילה 2, שחרור נעילה 1. חוט T2: לא יכול להשיג נעילה 1, שחרור נעילה 2. חוט T2: נעילה 2 נרכשת, מנסה להשיג נעילה 1. חוט T1: נעילה 1 נרכשת, מנסה להשיג נעילה 2. חוט T1: לא יכול להשיג את lock2, לשחרר את lock1. חוט T1: נעילה 1 נרכשת, מנסה לרכוש נעילה 2. חוט T2: לא יכול להשיג נעילה 1, שחרור נעילה 2. ..

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

3.3. הימנעות מ- Livelock

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

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

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

4. מסקנה

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

כמו תמיד, ניתן למצוא את הקוד השלם המשמש בדוגמה זו ב- GitHub.