כיצד המכונה הווירטואלית של ג'אווה מבצעת סנכרון חוטים

כל תוכניות Java מורכבות לקבצי מחלקה, המכילים קודי byt, שפת המכונה של המחשב הווירטואלי של Java. מאמר זה בוחן את אופן הטיפול בסנכרון החוטים על ידי המכונה הווירטואלית של ג'אווה, כולל קודי ה- BYT הרלוונטיים. (1,750 מילים)

החודש Under The Hood בוחן את סנכרון החוטים הן בשפת Java והן במכונה הווירטואלית של Java (JVM). מאמר זה הוא האחרון בסדרה הארוכה של מאמרי bytecode שהתחלתי בקיץ שעבר. הוא מתאר את שתי האופקודים היחידים שקשורים ישירות לסנכרון החוטים, את קוד האודי המשמשים לכניסה ויציאה ממסכים.

שרשורים ונתונים משותפים

אחת החוזקות של שפת התכנות ג'אווה היא התמיכה שלה ברב-הברגה ברמת השפה. חלק ניכר מתמיכה זו מתרכז בתיאום גישה לנתונים המשותפים בין מספר שרשורים.

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

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

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

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

מנעולי חפצים ומעמד

כמתואר לעיל, שני אזורי זיכרון במכונה הווירטואלית של Java מכילים נתונים המשותפים לכל הנושאים. אלו הם:

  • הערימה, המכילה את כל האובייקטים
  • אזור השיטה, המכיל את כל משתני המחלקה

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

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

מנעולי כיתה מיושמים למעשה כמנעולי חפצים. כאשר ה- JVM טוען קובץ מחלקה, הוא יוצר מופע של מחלקה java.lang.Class. כאשר אתה נועל כיתה, אתה למעשה נועל את Classהאובייקט של הכיתה הזו .

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

צגים

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

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

כאשר החוט עוזב את הבלוק, לא משנה איך הוא עוזב את הבלוק, הוא משחרר את הנעילה על האובייקט המשויך.

מנעולים מרובים

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

בלוקים מסונכרנים

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

הצהרות מסונכרנות

כדי ליצור משפט מסונכרן, אתה משתמש synchronizedבמילת המפתח עם ביטוי שמעריך להתייחסות לאובייקט, כמו reverseOrder()בשיטה הבאה:

class KitchenSync { private int[] intArray = new int[10]; void reverseOrder() { synchronized (this) { int halfWay = intArray.length / 2; for (int i = 0; i < halfWay; ++i) { int upperIndex = intArray.length - 1 - i; int save = intArray[upperIndex]; intArray[upperIndex] = intArray[i]; intArray[i] = save; } } } }

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

שני opcodes monitorenterו- monitorexit, משמשים לחסימת סינכרון בשיטות, כפי שמוצג בטבלה שלהלן.

לוח 1. צגים

אופקוד אופרנדים תיאור
monitorenter אף אחד pop objectref, רכוש את המנעול המשויך ל- objectref
monitorexit אף אחד פופ objectref, שחרר את המנעול הקשור ל- objectref

כאשר monitorenterנתקלים במכונה הווירטואלית של Java, היא רוכשת את הנעילה עבור האובייקט אליו מתייחסת objectref בערימה. אם החוט כבר מחזיק במנעול לאובייקט זה, ספירה מצטברת. בכל פעם monitorexitשמבוצעת עבור השרשור על האובייקט, הספירה מופחתת. כאשר הספירה מגיעה לאפס, המוניטור משתחרר.

התבונן ברצף הקוד המקודד שנוצר reverseOrder()בשיטת KitchenSyncהכיתה.

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

שיטות מסונכרנות

כדי לסנכרן שיטה שלמה, פשוט כלול את synchronizedמילת המפתח כאחת ממוקדמות השיטה, כמו ב:

class HeatSync { private int[] intArray = new int[10]; synchronized void reverseOrder() { int halfWay = intArray.length / 2; for (int i = 0; i < halfWay; ++i) { int upperIndex = intArray.length - 1 - i; int save = intArray[upperIndex]; intArray[upperIndex] = intArray[i]; intArray[i] = save; } } }

The JVM does not use any special opcodes to invoke or return from synchronized methods. When the JVM resolves the symbolic reference to a method, it determines whether the method is synchronized. If it is, the JVM acquires a lock before invoking the method. For an instance method, the JVM acquires the lock associated with the object upon which the method is being invoked. For a class method, it acquires the lock associated with the class to which the method belongs. After a synchronized method completes, whether it completes by returning or by throwing an exception, the lock is released.

Coming next month

Now that I have gone through the entire bytecode instruction set, I will be broadening the scope of this column to include various aspects or applications of Java technology, not just the Java virtual machine. Next month, I'll begin a multi-part series that gives an in-depth overview of Java's security model.

ביל ונטר כותב תוכנה באופן מקצועי מזה 12 שנים. הוא ממוקם בעמק הסיליקון, והוא מספק שירותי ייעוץ והדרכת תוכנה בשם Artima Company Company. במהלך השנים פיתח תוכנה לתעשיות אלקטרוניקה, חינוך, מוליכים למחצה וביטוח חיים. הוא תיכנת בשפות רבות בפלטפורמות רבות: שפת הרכבה במיקרו-מעבדים שונים, C ב- Unix, C ++ ב- Windows, Java באינטרנט. הוא מחבר הספר: Inside the Java Virtual Machine, בהוצאת מקגרו-היל.

למידע נוסף על נושא זה

  • The book The Java virtual machine Specification (//www.aw.com/cp/lindholm-yellin.html), by Tim Lindholm and Frank Yellin (ISBN 0-201-63452-X), part of The Java Series (//www.aw.com/cp/javaseries.html), from Addison-Wesley, is the definitive Java virtual machine reference.
  • Previous "Under The Hood" articles:
  • "The Lean, Mean Virtual Machine" Gives an introduction to the Java virtual machine.
  • "The Java Class File Lifestyle" Gives an overview to the Java class file, the file format into which all Java programs are compiled.
  • "Java's Garbage-Collected Heap" Gives an overview of garbage collection in general and the garbage-collected heap of the Java virtual machine in particular.
  • "Bytecode Basics" Introduces the bytecodes of the Java virtual machine, and discusses primitive types, conversion operations, and stack operations in particular.
  • "Floating Point Arithmetic" Describes the Java virtual machine's floating-point support and the bytecodes that perform floating point operations.
  • "Logic and Arithmetic" Describes the Java virtual machine's support for logical and integer arithmetic, and the related bytecodes.
  • "Objects and Arrays" Describes how the Java virtual machine deals with objects and arrays, and discusses the relevant bytecodes.
  • "Exceptions" Describes how the Java virtual machine deals with exceptions, and discusses the relevant bytecodes.
  • "נסה סוף סוף" מתאר כיצד המכונה הווירטואלית של ג'אווה מיישמת סעיפי ניסיון סוף סוף, ודן בקודי ה ByTec הרלוונטיים.
  • "בקרת זרימה" מתאר כיצד המכונה הווירטואלית של ג'אווה מיישמת את זרימת הבקרה ודן בקודי ה Bytec הרלוונטיים.
  • "הארכיטקטורה של אגלטס" מתאר את פעולתן הפנימית של אגלטס, טכנולוגיית סוכני התוכנה האוטונומית של יבמ מבוססת ג'אווה.
  • "הנקודה של אגלטות" מנתח את התועלת האמיתית של סוכנים ניידים כמו Aglets, טכנולוגיית סוכני התוכנה האוטונומית של יבמ המבוססת על Java.
  • "קריאת שיטה והחזרתה" מסביר כיצד המכונה הווירטואלית של ג'אווה קוראת וחוזרת משיטות, כולל קודי הביצוע הרלוונטיים.

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