CodeGym /בלוג Java /Random-HE /סנכרון שרשור. המפעיל המסונכרן
John Squirrels
רָמָה
San Francisco

סנכרון שרשור. המפעיל המסונכרן

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

מהו סנכרון ב-Java?

מחוץ לתחום התכנות, זה מרמז על סידור המאפשר לשני מכשירים או תוכניות לעבוד יחד. לדוגמה, ניתן לסנכרן סמארטפון ומחשב עם חשבון גוגל, וניתן לסנכרן חשבון אתר עם חשבונות ברשת חברתית כך שתוכל להשתמש בהם כדי להיכנס. לסינכרון שרשור יש משמעות דומה: זהו סידור שבו שרשורים מקיימים אינטראקציה עם אחד את השני. בשיעורים הקודמים, החוטים שלנו חיו ופעלו בנפרד זה מזה. אחד ביצע חישוב, שני ישן, והשלישי הציג משהו בקונסולה, אבל הם לא קיימו אינטראקציה. בתוכניות אמיתיות, מצבים כאלה הם נדירים. שרשורים מרובים יכולים לעבוד עם אותו מערך נתונים ולשנות אותו באופן פעיל. זה יוצר בעיות. תארו לעצמכם שרשורים מרובים כותבים טקסט לאותו מקום, למשל, לקובץ טקסט או לקונסולה. במקרה זה, הקובץ או המסוף הופכים למשאב משותף. השרשורים אינם מודעים לקיומו של זה, אז הם פשוט כותבים כל מה שהם יכולים בזמן שהוקצב להם על ידי מתזמן השרשור. בשיעור אחרון ראינו דוגמה לאן זה מוביל. בואו נזכיר את זה עכשיו: סנכרון שרשור.  המפעיל המסונכרן - 2הסיבה נעוצה בעובדה שהשרשורים עובדים עם משאב משותף (הקונסולה) מבלי לתאם את הפעולות שלהם זה עם זה. אם מתזמן השרשור מקצה זמן ל-Thread-1, אז הוא כותב הכל באופן מיידי לקונסולה. מה שרשורים אחרים יש או לא הספיקו לכתוב כבר לא משנה. התוצאה, כפי שאתה יכול לראות, מדכאת. זו הסיבה שהם הציגו קונספט מיוחד, ה- mutex (הדרה הדדית) , לתכנות מרובה הליכי. המטרה של mutex היא לספק מנגנון כך שלחוט אחד בלבד תהיה גישה לאובייקט בזמן מסוים. אם Thread-1 רוכש את המוטקס של אובייקט A, השרשורים האחרים לא יוכלו לגשת לאובייקט ולשנות אותו. השרשורים האחרים חייבים להמתין עד שהמוטקס של אובייקט A ישתחרר. הנה דוגמה מהחיים: דמיינו שאתם ועוד 10 זרים משתתפים בתרגיל. בתורנות, אתה צריך להביע את הרעיונות שלך ולדון במשהו. אבל בגלל שאתם רואים אחד את השני בפעם הראשונה, כדי לא להפריע כל הזמן ולעוף לזעם, אתם משתמשים ב'כדור מדבר': רק האדם עם הכדור יכול לדבר. כך בסופו של דבר יש לך דיון טוב ופורה. בעיקרו של דבר, הכדור הוא מוטקס. אם ה-mutex של אובייקט נמצא בידיו של חוט אחד, חוטים אחרים לא יכולים לעבוד עם האובייקט. אתה לא צריך לעשות שום דבר כדי ליצור mutex: הוא כבר מובנה במחלקה Object, מה שאומר שלכל אובייקט ב-Java יש אחד.

כיצד פועל המפעיל המסונכרן

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

   // ...Method logic
}
או כתוב בלוק קוד שבו הסנכרון מתבצע באמצעות אובייקט כלשהו:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...Some logic available simultaneously to all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
המשמעות פשוטה. אם שרשור אחד נכנס לתוך גוש הקוד המסומן במילת synchronizedהמפתח, הוא לוכד באופן מיידי את המוטקס של האובייקט, וכל שאר השרשורים שמנסים להיכנס לאותו בלוק או שיטה נאלצים להמתין עד שהשרשור הקודם ישלים את עבודתו וישחרר את המוניטור. סנכרון שרשור.  המפעיל המסונכרן - 3דרך אגב! במהלך הקורס, כבר ראית דוגמאות של synchronized, אבל הן נראו אחרת:
public void swap()
{
   synchronized (this)
   {
       // ...Method logic
   }
}
הנושא חדש עבורך. וכמובן, יהיה בלבול עם התחביר. לכן, שנן אותו מיד כדי להימנע מבלבול מאוחר יותר מהדרכים השונות לכתיבתו. שתי הדרכים האלה לכתוב את זה אומרות אותו הדבר:
public void swap() {

   synchronized (this)
   {
       // ...Method logic
   }
}


public synchronized void swap() {

   }
}
במקרה הראשון, אתה יוצר בלוק קוד מסונכרן מיד עם הכניסה לשיטה. הוא מסונכרן על ידי thisהאובייקט, כלומר האובייקט הנוכחי. ובדוגמה השנייה, אתה מיישם את synchronizedמילת המפתח על כל השיטה. זה הופך את זה למיותר לציין במפורש את האובייקט המשמש לסנכרון. מכיוון שכל השיטה מסומנת במילת המפתח, השיטה תסונכרן אוטומטית עבור כל המופעים של המחלקה. לא נצלול לדיון באיזו דרך עדיפה. לעת עתה, בחר את הדרך שאתה הכי אוהב :) העיקר לזכור: אתה יכול להכריז על שיטה מסונכרנת רק כאשר כל ההיגיון שלה מבוצע על ידי שרשור אחד בכל פעם. לדוגמה, תהיה זו טעות לסנכרן את doSomething()השיטה הבאה:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...Some logic available simultaneously to all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
כפי שניתן לראות, חלק מהשיטה מכיל לוגיקה שאינה דורשת סנכרון. ניתן להפעיל את הקוד הזה על ידי מספר שרשורים בו-זמנית, וכל המקומות הקריטיים מופרדים בבלוק נפרד synchronized. ועוד דבר אחד. הבה נבחן מקרוב את הדוגמה שלנו מהשיעור עם החלפת שמות:
public void swap()
{
   synchronized (this)
   {
       // ...Method logic
   }
}
הערה: הסנכרון מתבצע באמצעותthis. כלומר, שימושMyClassבאובייקט מסוים. נניח שיש לנו 2 חוטים (Thread-1וThread-2) ורקMyClass myClassאובייקט אחד. במקרה זה, אםThread-1יקראmyClass.swap()לשיטה, המוטקס של האובייקט יהיה תפוס, וכאשר מנסים להתקשר השיטהmyClass.swap()תתקעThread-2בזמן ההמתנה לשחרור המוטקס. אם יהיו לנו 2 פתילים ו 2MyClassאובייקטים (myClass1וmyClass2), ה-threads שלנו יכולים בקלות לבצע בו זמנית את השיטות המסונכרנות על אובייקטים שונים. השרשור הראשון מבצע את זה:
myClass1.swap();
השני מבצע את זה:
myClass2.swap();
במקרה זה, synchronizedמילת המפתח בתוך swap()השיטה לא תשפיע על פעולת התוכנית, מכיוון שהסנכרון מתבצע באמצעות אובייקט ספציפי. ובמקרה האחרון, יש לנו 2 אובייקטים. לפיכך, השרשורים אינם יוצרים בעיות זה עבור זה. אחרי הכל, לשני אובייקטים יש 2 מוטקסים שונים, ורכישת אחד אינה תלויה ברכישת השני .

תכונות מיוחדות של סנכרון בשיטות סטטיות

אבל מה אם אתה צריך לסנכרן שיטה סטטית ?
class MyClass {
   private static String name1 = "Ally";
   private static String name2 = "Lena";

   public static synchronized void swap() {
       String s = name1;
       name1 = name2;
       name2 = s;
   }

}
לא ברור איזה תפקיד ישחק כאן המוטקס. הרי כבר קבענו שלכל אובייקט יש mutex. אבל הבעיה היא שאנחנו לא צריכים אובייקטים כדי לקרוא לשיטה MyClass.swap(): השיטה היא סטטית! אז מה הבא? :/ למעשה אין כאן בעיה. היוצרים של Java דאגו להכל :) אם מתודה המכילה לוגיקה במקביל קריטית היא סטטית, אז הסנכרון מתבצע ברמת המחלקה. לבהירות רבה יותר, אנו יכולים לכתוב מחדש את הקוד לעיל באופן הבא:
class MyClass {
   private static String name1 = "Ally";
   private static String name2 = "Lena";

   public static void swap() {

       synchronized (MyClass.class) {
           String s = name1;
           name1 = name2;
           name2 = s;
       }
   }

}
באופן עקרוני, יכולת לחשוב על זה בעצמך: מכיוון שאין אובייקטים, מנגנון הסנכרון חייב איכשהו להיות אפוי לתוך המחלקה עצמה. וככה זה: אנחנו יכולים להשתמש בשיעורים כדי לסנכרן.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION