מדריך למילת המפתח המסונכרנת בג'אווה

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

מאמר מהיר זה יהיה מבוא לשימוש ב- מסונכרן לחסום ב- Java.

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

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

2. מדוע סנכרון?

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

מחלקה ציבורית BaeldungSynchronizedMethods {private int sum = 0; חלל ציבורי לחשב () {setSum (getSum () + 1); } // קובעי סטנדרטים וגטרים} 

ובואו נכתוב מבחן פשוט:

@Test הציבור בטל givenMultiThread_whenNonSyncMethod () {שירות ExecutorService = Executors.newFixedThreadPool (3); BaeldungSynchronizedMethods סיכום = BaeldungSynchronizedMethods חדשים (); IntStream.range (0, 1000). ForEach (ספירה -> service.submit (סיכום :: לחשב)); service.awaitTermination (1000, TimeUnit.MILLISECONDS); assertEquals (1000, summation.getSum ()); }

אנחנו פשוט משתמשים ב- שירות ExecutorService עם בריכה עם 3 פתילים לביצוע לחשב() 1000 פעמים.

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

java.lang.AssertionError: צפוי: אבל היה: at org.junit.Assert.fail (Assert.java:88) at org.junit.Assert.failNotEquals (Assert.java:834) ...

תוצאה זו כמובן אינה צפויה.

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

3. ה מסונכרן מילת מפתח

ה מסונכרן ניתן להשתמש במילת מפתח ברמות שונות:

  • שיטות מופע
  • שיטות סטטיות
  • חסימות קוד

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

3.1. מסונכרן שיטות מופע

כל שעליך לעשות הוא להוסיף את מסונכרן מילת מפתח בהצהרת השיטה כדי להפוך את השיטה לסינכרונית:

חלל מסונכרן פומבי מסונכרןCalculate () {setSum (getSum () + 1); }

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

@Test ציבורי בטל givenMultiThread_whenMethodSync () {שירות ExecutorService = Executors.newFixedThreadPool (3); שיטת SynchronizedMethods = SynchronizedMethods חדשים (); IntStream.range (0, 1000). ForEach (ספירה -> service.submit (שיטה :: synchronisedCalculate)); service.awaitTermination (1000, TimeUnit.MILLISECONDS); assertEquals (1000, method.getSum ()); }

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

3.2. מסונכרן סטטיג שיטות

שיטות סטטיות הן מסונכרן בדיוק כמו שיטות מופע:

 ציבור סטטי מסונכרן חלל ריק syncStaticCalculate () {staticSum = staticSum + 1; }

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

בואו נבדוק את זה:

@Test הציבור בטל givenMultiThread_whenStaticSyncMethod () {שירות ExecutorService = Executors.newCachedThreadPool (); IntStream.range (0, 1000). ForEach (ספירה -> service.submit (BaeldungSynchronizedMethods :: syncStaticCalculate)); service.awaitTermination (100, TimeUnit.MILLISECONDS); assertEquals (1000, BaeldungSynchronizedMethods.staticSum); }

3.3. מסונכרן חסימות בשיטות

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

חלל ציבורי performSynchronisedTask () {synchronised (this) {setCount (getCount () + 1); }}

בואו נבדוק את השינוי:

@Test הציבורי בטל givenMultiThread_whenBlockSync () {שירות ExecutorService = Executors.newFixedThreadPool (3); BaeldungSynchronizedBlocks synchronizedBlocks = BaeldungSynchronizedBlocks חדש (); IntStream.range (0, 1000) .forEach (ספירה -> service.submit (synchronizedBlocks :: performSynchronisedTask)); service.awaitTermination (100, TimeUnit.MILLISECONDS); assertEquals (1000, synchronizedBlocks.getCount ()); }

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

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

חלל סטטי ציבורי performStaticSyncTask () {synchronized (SynchronisedBlocks.class) {setStaticCount (getStaticCount () + 1); }}

בואו נבדוק את הבלוק בתוך סטָטִי שיטה:

@Test הציבור בטל givenMultiThread_whenStaticSyncBlock () {שירות ExecutorService = Executors.newCachedThreadPool (); IntStream.range (0, 1000). ForEach (ספירה -> service.submit (BaeldungSynchronizedBlocks :: performStaticSyncTask)); service.awaitTermination (100, TimeUnit.MILLISECONDS); assertEquals (1000, BaeldungSynchronizedBlocks.getStaticCount ()); }

3.4. כניסה חוזרת

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

נעילת עצמים = אובייקט חדש (); מסונכרן (נעילה) {System.out.println ("לראשונה רוכשת אותו"); מסונכרן (נעילה) {System.out.println ("נכנס שוב"); מסונכרן (נעילה) {System.out.println ("ושוב"); }}}

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

4. מסקנה

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

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

הקוד השלם להדרכה זו זמין באתר GitHub.