שימוש באובייקט Mutex בג'אווה

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

במדריך זה נראה דרכים שונות ליישום mutex ב- Java.

2. מוטקס

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

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

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

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

3. מדוע Mutex?

ראשית, ניקח דוגמא של א SequenceGeneraror class, שמייצר את הרצף הבא על ידי הגדלת ה- ערך נוכחי אחד בכל פעם:

מחלקה ציבורית SequenceGenerator {private int currentValue = 0; public int getNextSequence () {currentValue = currentValue + 1; החזר currentValue; }}

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

@Test הציבור בטל givenUnsafeSequenceGenerator_whenRaceCondition_thenUnexpectedBehavior () זורק חריג {int count = 1000; הגדר ייחודיות = getUniqueSequences (חדש SequenceGenerator (), ספירה); Assert.assertEquals (count, uniqueSequences.size ()); } הגדר פרטי getUniqueSequences (מחולל SequenceGenerator, int count) זורק Exception {ExecutorService executor = Executors.newFixedThreadPool (3); הגדר uniqueSequences = חדש LinkedHashSet (); רשימה עתיד = ArrayList חדש (); עבור (int i = 0; i <count; i ++) {futures.add (executor.submit (מחולל :: getNextSequence)); } עבור (עתיד עתידי: עתיד) {uniqueSequences.add (future.get ()); } executor.awaitTermination (1, TimeUnit.SECONDS); executor.shutdown (); להחזיר ייחודי רצפים; }

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

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

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

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

ישנן דרכים שונות, אנו יכולים ליישם mutex ב- Java. אז, הבא, נראה את הדרכים השונות ליישם mutex עבורנו SequenceGenerator מעמד.

4. שימוש מסונכרן מילת מפתח

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

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

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

בוא נשנה getNextSequence שיהיה לך mutex, פשוט על ידי הוספת ה- מסונכרן מילת מפתח:

מחלקה ציבורית SequenceGeneratorUsingSynchronizedMethod מרחיב SequenceGenerator {@Override public synchronized int getNextSequence () {return super.getNextSequence (); }}

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

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

מחלקה ציבורית SequenceGeneratorUsingSynchronizedBlock מרחיב SequenceGenerator {אובייקט פרטי mutex = אובייקט חדש (); @Override int intentNextSequence () {מסונכרן (mutex) {להחזיר super.getNextSequence (); }}}

5. שימוש ReentrantLock

ה ReentrantLock הכיתה הוצגה ב- Java 1.5. זה מספק יותר גמישות ושליטה מאשר ה- מסונכרן גישת מילות מפתח.

בואו נראה איך נוכל להשתמש ב- ReentrantLock כדי להשיג הרחקה הדדית:

מחלקה ציבורית SequenceGeneratorUsingReentrantLock מרחיב SequenceGenerator {פרטי ReentrantLock mutex = ReentrantLock חדש (); @Override ציבורי int getNextSequence () {נסה {mutex.lock (); החזר super.getNextSequence (); } סוף סוף {mutex.unlock (); }}}

6. שימוש סֵמָפוֹר

כמו ReentrantLock, ה סֵמָפוֹר הכיתה הוצגה גם ב- Java 1.5.

בעוד שבמקרה של mutex רק חוט אחד יכול לגשת למקטע קריטי, סֵמָפוֹר מאפשר מספר קבוע של שרשורים לגישה למקטע קריטי. לָכֵן, אנו יכולים גם ליישם mutex על ידי הגדרת מספר האשכולות המותרות ב- סֵמָפוֹר לאחד.

בואו ניצור כעת גרסה אחרת הבטוחה לשרשור SequenceGenerator באמצעות סֵמָפוֹר:

מעמד ציבורי SequenceGeneratorUsingSemaphore מרחיב SequenceGenerator {Semaphore mutex פרטי = סמפור חדש (1); @Override ציבורי int getNextSequence () {נסה {mutex.acquire (); החזר super.getNextSequence (); } לתפוס (InterruptedException e) {// קוד טיפול בחריג} לבסוף {mutex.release (); }}}

7. שימוש בגויאבה צג מעמד

עד כה ראינו את האפשרויות ליישום mutex באמצעות תכונות המסופקות על ידי Java.

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

ראשית, נוסיף את התלות של Maven עבור גויאבה:

 com.google.guava גויאבה 28.0-jre 

כעת נכתוב תת-מחלקה נוספת של SequenceGenerator משתמש ב צג מעמד:

מחלקה ציבורית SequenceGeneratorUsingMonitor מרחיב SequenceGenerator {צג פרטי mutex = צג חדש (); @Override ציבורי int getNextSequence () {mutex.enter (); נסה {להחזיר super.getNextSequence (); } סוף סוף {mutex.leave (); }}}

8. מסקנה

במדריך זה בדקנו את הרעיון של mutex. כמו כן, ראינו את הדרכים השונות ליישמו ב- Java.

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


$config[zx-auto] not found$config[zx-overlay] not found