JavaRush /בלוג Java /Random-HE /ריבוי השרשורים בג'אווה: מהות, יתרונות ומלכודות נפוצות

ריבוי השרשורים בג'אווה: מהות, יתרונות ומלכודות נפוצות

פורסם בקבוצה
שלום! קודם כל, מזל טוב: הגעתם לנושא Multithreading בג'אווה! זה הישג רציני, הדרך עוד ארוכה. אבל תתכוננו: זה אחד הנושאים הקשים בקורס. והעניין הוא לא שמשתמשים כאן במחלקות מורכבות או בשיטות רבות: להיפך, אין אפילו שני תריסר. זה יותר שאתה צריך לשנות קצת את החשיבה שלך. בעבר, התוכניות שלך בוצעו ברצף. כמה שורות קוד עקבו אחרי אחרות, כמה שיטות בעקבות אחרות, ובסך הכל הכל היה ברור. ראשית, חשב משהו, לאחר מכן הצג את התוצאה במסוף, ולאחר מכן סיים את התוכנית. כדי להבין ריבוי שרשורים, עדיף לחשוב במונחים של מקבילות. נתחיל במשהו מאוד פשוט :) ריבוי השרשורים בג'אווה: מהות, יתרונות ומלכודות נפוצות - 1דמיינו שהמשפחה שלכם עוברת מבית אחד למשנהו. חלק חשוב במעבר דירה הוא אריזת הספרים. צברתם הרבה ספרים, ואתם צריכים לשים אותם בקופסאות. עכשיו רק אתה חופשי. אמא מכינה אוכל, אח אוסף בגדים, ואחותי הלכה לחנות. לבד אתה יכול לנהל, לפחות, ובמוקדם או במאוחר, אפילו תסיים את המשימה בעצמך, אבל זה ייקח הרבה זמן. עם זאת, בעוד 20 דקות אחותך תחזור מהחנות, ואין לה מה לעשות. אז היא תוכל להצטרף אליך. המשימה נותרה בעינה: הכניסו את הספרים לקופסאות. זה פשוט פועל פי שניים מהר יותר. למה? כי העבודה נעשית במקביל. שני "חוטים" שונים (אתה ואחותך) מבצעים בו זמנית את אותה משימה, ואם שום דבר לא ישתנה, הפרש השעות יהיה גדול מאוד בהשוואה למצב שבו הייתם עושים הכל לבד. אם אחיך יסיים את המשימה שלו בקרוב, הוא יכול לעזור לך, והדברים ילכו אפילו יותר מהר.

בעיות ש-multithreading פותר ב-Java

בעיקרו של דבר, Java multithreading הומצא כדי לפתור שתי בעיות עיקריות:
  1. בצע מספר פעולות בו זמנית.

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

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

    ובכן, בוא נחכה כמה דקות!

    ריבוי השרשורים בג'אווה: מהות, יתרונות ומלכודות נפוצות - 3

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

  2. להאיץ את החישובים.

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

אבל, כפי שכבר קראתם בהרצאה, מערכות מודרניות הן מאוד חכמות, ואפילו על ליבת מחשוב אחת הן מסוגלות ליישם מקביליות, או פסאודו-מקביליות, כאשר משימות מבוצעות לסירוגין. בואו נעבור מדברים כלליים לדברים ספציפיים ונכיר את המחלקה הראשית בספריית ג'אווה הקשורה ל-multithreading - java.lang.Thread. באופן קפדני, שרשורים ב-Java מיוצגים על ידי מופעים של המחלקה Thread. כלומר, כדי ליצור ולהפעיל 10 שרשורים, תזדקק ל-10 אובייקטים מהמחלקה הזו. נכתוב את הדוגמה הפשוטה ביותר:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("I'm Thread! My name is " + getName());
   }
}
כדי ליצור ולהפעיל שרשורים, עלינו ליצור מחלקה ולרשת אותה מה- java.lang. Threadולעקוף את השיטה בו run(). האחרון חשוב מאוד. בשיטה run()אנו קובעים את ההיגיון שהשרשור שלנו חייב לבצע. כעת, אם ניצור מופע MyFirstThreadונפעיל אותו, השיטה run()תדפיס קו עם השם שלה לקונסולה: השיטה getName()מדפיסה את שם ה"מערכת" של השרשור, המוקצה אוטומטית. למרות שבעצם, למה "אם"? בואו ליצור ולבדוק!
public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
פלט מסוף: אני שרשור! שמי הוא Thread-2 I'm Thread! שמי הוא שרשור-1 אני חוט! שמי הוא Thread-0 I'm Thread! שמי הוא Thread-3 I'm Thread! שמי הוא Thread-6 I'm Thread! שמי הוא Thread-7 I'm Thread! שמי הוא Thread-4 I'm Thread! שמי הוא Thread-5 I'm Thread! שמי הוא Thread-9 I'm Thread! שמי Thread-8 אנו יוצרים 10 שרשורים (אובייקטים) MyFirstThreadשיורשים מהם Threadומשיקים אותם על ידי קריאה לשיטת האובייקט start(). לאחר קריאה למתודה , start()השיטה שלה מתחילה לעבוד run(), והלוגיקה שנכתבה בה מבוצעת. שימו לב: שמות השרשור אינם מסודרים. זה די מוזר, למה הם לא הוצאו להורג בתורם: Thread-0, Thread-1, Thread-2וכן הלאה? זו בדיוק דוגמה לכך שחשיבה סטנדרטית, "רציפה" לא תעבוד. העובדה היא שבמקרה זה אנו מוציאים רק פקודות כדי ליצור ולהפעיל 10 שרשורים. באיזה סדר יש להשיק אותם קובע מתזמן השרשור: מנגנון מיוחד בתוך מערכת ההפעלה. איך זה בדיוק בנוי ועל פי איזה עיקרון הוא מקבל החלטות זה נושא מאוד מורכב, ולא נצלול אליו עכשיו. הדבר העיקרי שיש לזכור הוא שהמתכנת לא יכול לשלוט ברצף ביצוע השרשור. כדי להבין את חומרת המצב, נסה להפעיל את השיטה main()מהדוגמה למעלה עוד כמה פעמים. פלט קונסולה שני: I'm Thread! שמי הוא Thread-0 I'm Thread! שמי הוא Thread-4 I'm Thread! שמי הוא Thread-3 I'm Thread! שמי הוא Thread-2 I'm Thread! שמי הוא שרשור-1 אני חוט! שמי הוא Thread-5 I'm Thread! שמי הוא Thread-6 I'm Thread! שמי הוא Thread-8 I'm Thread! שמי הוא Thread-9 I'm Thread! שמי הוא Thread-7 פלט קונסולה שלישית: I'm Thread! שמי הוא Thread-0 I'm Thread! שמי הוא Thread-3 I'm Thread! שמי הוא שרשור-1 אני חוט! שמי הוא Thread-2 I'm Thread! שמי הוא Thread-6 I'm Thread! שמי הוא Thread-4 I'm Thread! שמי הוא Thread-9 I'm Thread! שמי הוא Thread-5 I'm Thread! שמי הוא Thread-7 I'm Thread! שמי הוא Thread-8

בעיות ש-multithreading יוצר

בדוגמה עם ספרים, ראית ש-multithreading פותר בעיות חשובות למדי, והשימוש בו מאיץ את עבודת התוכניות שלנו. במקרים רבים - פעמים רבות. אבל לא בכדי ריבוי השרשורים נחשב לנושא מורכב. אחרי הכל, אם משתמשים בו בצורה לא נכונה, זה יוצר בעיות במקום לפתור אותן. כשאני אומר "צור בעיות", אני לא מתכוון למשהו מופשט. ישנן שתי בעיות ספציפיות ש-multithreading יכול לגרום: מבוי סתום ומצב גזע. מבוי סתום הוא מצב שבו שרשורים מרובים ממתינים למשאבים שנכבשו זה על ידי זה, ואף אחד מהם לא יכול להמשיך בביצוע. נדבר על זה יותר בהרצאות עתידיות, אבל לעת עתה הדוגמה הזו תספיק: ריבוי השרשורים בג'אווה: מהות, יתרונות ומלכודות נפוצות - 4 תארו לעצמכם ש-thread-1 עובד עם אובייקט-1 כלשהו, ​​ו-thread-2 עובד עם אובייקט-2. התוכנית כתובה כך:
  1. Thread-1 יפסיק לעבוד עם Object-1 ויעבור ל-Object-2 ברגע ש-Thread-2 יפסיק לעבוד עם Object 2 ויעבור ל-Object-1.
  2. Thread-2 יפסיק לעבוד עם Object-2 ויעבור ל-Object-1 ברגע ש-Thread-1 יפסיק לעבוד עם Object 1 ויעבור ל-Object-2.
גם ללא ידע מעמיק בריבוי שרשורים, אתה יכול בקלות להבין שלא ייצא מזה כלום. החוטים לעולם לא ישנו מקום ויחכו זה לזה לנצח. השגיאה נראית ברורה, אבל במציאות היא לא. אתה יכול בקלות לאפשר את זה להיכנס לתוכנית. נסתכל על דוגמאות לקוד שגורם למבוי סתום בהרצאות הבאות. אגב, ל-Quora יש דוגמה מצוינת מהחיים האמיתיים שמסבירה מה זה מבוי סתום . "במדינות מסוימות בהודו, לא ימכרו לך קרקע חקלאית אלא אם כן אתה רשום כחקלאי. עם זאת, לא תירשם כחקלאי אם אין לך קרקע חקלאית”. מעולה, מה אני יכול להגיד! :) עכשיו לגבי מצב המירוץ - מצב המירוץ. ריבוי השרשורים בג'אווה: מהות, יתרונות ומלכודות נפוצות - 5תנאי מירוץ הוא פגם בתכנון במערכת או באפליקציה מרובת הליכי שרשרת שבה פעולת המערכת או האפליקציה תלויה בסדר ביצוע חלקים מהקוד. זכור את הדוגמה עם שרשורים רצים:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Выполнен поток " + getName());
   }
}

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
עכשיו דמיינו שהתוכנית אחראית לתפעול של רובוט שמכין אוכל! חוט-0 מוציא את הביצים מהמקרר. זרם 1 מדליק את הכיריים. Stream-2 מוציא מחבת ומניח אותה על הכיריים. זרם 3 מדליק אש על הכיריים. זרם 4 שופך שמן למחבת. זרם 5 שובר את הביצים ושופך אותן לטיגון. זרם 6 זורק את הפגזים לפח האשפה. Stream-7 מסיר את הביצים הטרופות המוגמרות מהאש. פוטוק-8 מניח ביצים מקושקשות בצלחת. Stream-9 שוטף כלים. תסתכל על התוצאות של התוכנית שלנו: שרשור-0 הוצא לפועל שרשור-2 שרשור הוצא לפועל חוט-1 שרשור הוצא לפועל חוט-4 שרשור הוצא לפועל חוט-5 שרשור הוצא לפועל שרשור-8 שרשור הוצא לפועל אשכול-7 שרשור הופעל -3 שרשור-6 הוצא לפועל. האם התסריט מהנה? :) והכל כי פעולת התוכנית שלנו תלויה בסדר ביצוע השרשורים. בהפרה הקלה ביותר של הרצף, המטבח שלנו הופך לגיהנום, ורובוט שהשתגע הורס את כל מה שמסביבו. זו גם בעיה נפוצה בתכנות מרובה-תשריט, עליה תשמעו יותר מפעם אחת. בסיום ההרצאה ברצוני להמליץ ​​לכם על ספר על ריבוי חוטים.
ריבוי שרשורים בג'אווה: מהות, יתרונות ומלכודות נפוצות - 6
"מקבילות ג'אווה בתרגול" נכתב עוד בשנת 2006, אך לא איבד מהרלוונטיות שלו. הוא מכסה תכנות מרובה-תכליות ב-Java, החל מהיסודות וכלה ברשימה של השגיאות והאנטי-דפוסים הנפוצים ביותר. אם אי פעם תחליט להיות גורו תכנות מרובה חוטים, ספר זה הוא חובה לקרוא. נתראה בהרצאות הבאות! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION