CodeGym /בלוג Java /Random-HE /ריבוי השרשורים ב-Java
John Squirrels
רָמָה
San Francisco

ריבוי השרשורים ב-Java

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

בעיות נפתרות על ידי ריבוי פתילים

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

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

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

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

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

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

  2. בצע חישובים מהר יותר.

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

אבל כפי שכבר קראתם בשיעור הזה, המערכות של היום מאוד חכמות, ואפילו בליבת מחשוב אחת מסוגלות להשיג מקביליות, או ליתר דיוק פסאודו-מקבילות, שבה משימות מבוצעות לסירוגין. בואו נעביר כלליות לפרטים ונכיר את המחלקה החשובה ביותר בספריית ריבוי ההליכים של Java - 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 . מחלקת שרשור , ודרוס את שיטת run() שלה . הדרישה האחרונה חשובה מאוד. בשיטת run() אנו מגדירים את ההיגיון לביצוע השרשור שלנו. כעת, אם ניצור ונפעיל מופע של MyFirstThread , השיטה run() תציג שורה עם שם: השיטה getName() מציגה את שם ה-'system' של השרשור, המוקצה אוטומטית. אבל למה אנחנו מדברים בהיסוס? בואו ליצור אחד ולגלות!
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 objects, שיורשים את Thread ) ונתחיל אותם על ידי קריאה למתודה start() בכל אובייקט. לאחר קריאה למתודה start() , ההיגיון בשיטת run() מבוצע. הערה: שמות השרשור אינם מסודרים. זה מוזר שהם לא היו ברצף: Thread-0 , Thread-1 , Thread-2 , וכן הלאה? כפי שזה קורה, זו דוגמה לתקופה שבה חשיבה 'רציפה' אינה מתאימה. הבעיה היא שסיפקנו רק פקודות ליצירה והרצה של 10 שרשורים. מתזמן השרשור, מנגנון מיוחד של מערכת הפעלה, מחליט את סדר הביצוע שלהם. העיצוב המדויק שלו ואסטרטגיית קבלת ההחלטות הם נושאים לדיון עמוק שלא נצלול אליו כרגע. הדבר העיקרי שיש לזכור הוא שהמתכנת לא יכול לשלוט בסדר הביצוע של שרשורים. כדי להבין את חומרת המצב, נסה להפעיל את שיטת main() בדוגמה למעלה עוד כמה פעמים. פלט מסוף בריצה שנייה: אני שרשור! שמי הוא 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 Console פלט מהריצה השלישית: 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 יכול ליצור: מבוי סתום ותנאי מירוץ. מבוי סתום הוא מצב שבו שרשורים מרובים ממתינים למשאבים המוחזקים זה בזה, ואף אחד מהם לא יכול להמשיך לרוץ. נדבר על זה יותר בשיעורים הבאים. הדוגמה הבאה תספיק לעת עתה: ריבוי שרשורים בג'אווה: מה זה, היתרונות שלו והמלכודות הנפוצות - 4תארו לעצמכם ש-Thread-1 מקיים אינטראקציה עם אובייקט-1 כלשהו, ​​וש-Thread-2 מקיים אינטראקציה עם אובייקט-2. יתר על כן, התוכנית כתובה כך:
  1. Thread-1 מפסיק את האינטראקציה עם Object-1 ועובר ל-Object-2 ברגע ש-Thread-2 מפסיק את האינטראקציה עם Object-2 ועובר ל-Object-1.
  2. Thread-2 מפסיק את האינטראקציה עם אובייקט-2 ועובר ל-Object-1 ברגע ש-Thread-1 מפסיק את האינטראקציה עם Object-1 ועובר ל-Object-2.
גם ללא הבנה מעמיקה של ריבוי הליכי שרשור, אתה יכול בקלות לראות ששום דבר לא יקרה. החוטים לעולם לא יחליפו מקומות ויחכו זה לזה לנצח. השגיאה נראית ברורה, אבל במציאות היא לא. אתה יכול לעשות זאת בקלות בתוכנית. נשקול דוגמאות לקוד שגורם למבוי סתום בשיעורים הבאים. אגב, ל-Quora יש דוגמה מצוינת מהחיים האמיתיים שמסבירה מה זה מבוי סתום . ״במדינות מסוימות בהודו, לא ימכרו לך קרקע חקלאית אלא אם אתה חקלאי רשום. עם זאת, הם לא ירשמו אותך כחקלאי אם אין לך קרקע חקלאית'. גדול! מה אנחנו יכולים להגיד?! :) עכשיו בואו נדבר על תנאי המירוץ. תנאי מירוץ הוא שגיאת תכנון במערכת או אפליקציה מרובה הליכי, כאשר פעולת המערכת או האפליקציה תלויה בסדר ביצוע חלקים מהקוד. זכור, הדוגמה שלנו שבה התחלנו שרשורים:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Thread executed: " + 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 מדליק את הכיריים. חוט-2 מקבל מחבת ומניח אותו על הכיריים. חוט-3 מדליק את הכיריים. חוט-4 יוצק שמן למחבת. חוט-5 שובר את הביצים ויוצק אותן למחבת. חוט-6 זורק את קליפות הביצים לפח האשפה. חוט-7 מוציא את הביצים המבושלות מהמבער. חוט-8 מניח את הביצים המבושלות על צלחת. חוט-9 שוטף את הכלים. תסתכל על התוצאות של התוכנית שלנו: שרשור הוצא לפועל: שרשור-0 שרשור הוצא: שרשור-2 התבצע שרשור-1 שרשור הוצא לפועל: שרשור-4 הוצא לפועל: שרשור-9 שרשור הופעל: שרשור-5 השרשור הופעל: אשכול-8 הוצא להורג: שרשור-7 שרשור הוצא להורג: שרשור-3 האם זו שגרת קומדיה? :) והכל בגלל שעבודת התוכנית שלנו תלויה בסדר הביצוע של השרשורים. בהתחשב בהפרה הקטנה ביותר של הרצף הנדרש, המטבח שלנו הופך לגיהנום, ורובוט מטורף הורס את כל מה שמסביבו. זו גם בעיה נפוצה בתכנות מרובי-הליכים. אתה תשמע על זה יותר מפעם אחת. בסיום שיעור זה, ברצוני להמליץ ​​על ספר על ריבוי שרשורים. ריבוי שרשורים בג'אווה: מה זה, היתרונות שלו והמלכודות הנפוצות - 6'Java Concurrency in Practice' נכתב ב-2006, אך לא איבד מהרלוונטיות שלו. הוא מוקדש לתכנות ג'אווה עם ריבוי הליכי - מהיסודות ועד לטעויות והאנטי-דפוסים הנפוצים ביותר. אם יום אחד תחליט להפוך לגורו של ריבוי שרשורים, ספר זה הוא ספר חובה. נתראה בשיעורים הבאים! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION