מדריך java.util.concurrent.Locks

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

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

ה לנעול הממשק קיים מאז Java 1.5. זה מוגדר בתוך java.util.concurrent.lock החבילה והיא מספקת פעולות נרחבות לנעילה.

במאמר זה נחקור יישומים שונים של ה- לנעול ממשק ויישומיהם.

2. ההבדלים בין נעילה לחסימה מסונכרנת

ישנם מעט הבדלים בין השימוש בסינכרון לַחסוֹם ושימוש לנעול ממשקי API:

  • א מסונכרןלַחסוֹם כלול במלואו בשיטה - אנחנו יכולים לקבל לנעול ממשקי API לנעול() ו לבטל נעילה() פעולה בשיטות נפרדות
  • כפי שבלוק מסונכרן אינו תומך בהגינות, כל שרשור יכול לרכוש את המנעול לאחר שחרורו, לא ניתן לציין העדפה. אנו יכולים להשיג הוגנות בתוך לנעול ממשקי API על ידי ציון ה- הֲגִינוּת תכונה. זה מוודא כי חוט ההמתנה הארוך ביותר יקבל גישה למנעול
  • פתיל נחסם אם הוא לא יכול לקבל גישה לסנכרון לַחסוֹם. ה לנעול API מספק tryLock () שיטה. החוט רוכש נעילה רק אם הוא זמין ולא מוחזק על ידי שום חוט אחר. זה מקטין את זמן החסימה של חוט המתנה לנעילה
  • חוט שנמצא במצב "מחכה" לרכישת הגישה אליו בלוק מסונכרן, לא ניתן להפריע. ה לנעול API מספק שיטה נעילה להפריע () שניתן להשתמש בהם כדי להפריע לשרשור כאשר הוא ממתין לנעילה

3. לנעול ממשק API

בואו נסתכל על השיטות ב- לנעול מִמְשָׁק:

  • נעילת חלל ()רכוש את המנעול אם הוא זמין; אם המנעול אינו זמין חוט נחסם עד לשחרור המנעול
  • נעילה בטלה באופן מפריע () זה דומה ל לנעול(), אבל זה מאפשר להפריע לשרשור החסום ולחדש את הביצוע באמצעות זריקה java.lang.InterruptedException
  • tryLock בוליאני ()- זו גרסה שאינה חוסמת את לנעול() שיטה; היא מנסה לרכוש את המנעול באופן מיידי, להחזיר אמת אם הנעילה תצליח
  • tryLock בוליאני (פסק זמן ארוך, TimeUnit timeUnit)זה דומה ל tryLock (), אלא שהוא ממתין לפסק הזמן הנתון לפני שהוא מוותר על הניסיון לרכוש את לנעול
  • בָּטֵל לבטל נעילה() - פותח את הנעילה לנעול למשל

תמיד יש לבטל נעילה של מופע נעול כדי להימנע ממצב סתום. בלוק קוד מומלץ לשימוש במנעול צריך להכיל נסה לתפוס ו סוף כל סוף לַחסוֹם:

נעילת נעילה = ...; lock.lock (); נסה {// גישה למשאב המשותף} סוף סוף {lock.unlock (); }

בנוסף ל לנעול מִמְשָׁק, יש לנו ReadWriteLock ממשק השומר על זוג מנעולים, אחד לפעולות קריאה בלבד ואחד לפעולת הכתיבה. ניתן להחזיק את נעילת הקריאה בו זמנית על ידי מספר שרשורים כל עוד אין כתיבה.

ReadWriteLock מצהיר על שיטות לרכישת מנעולי קריאה או כתיבה:

  • נעילת readLock ()מחזיר את המנעול המשמש לקריאה
  • נעילת כתיבת נעילה () - מחזיר את המנעול המשמש לכתיבה

4. נעילת יישומים

4.1. ReentrantLock

ReentrantLock הכיתה מיישמת את לנעול מִמְשָׁק. הוא מציע את אותה סמנטיקה מקבילה וזיכרון, כמו גישה למנעול המסך המרומז מסונכרן שיטות והצהרות, עם יכולות מורחבות.

בואו נראה, איך נוכל להשתמש ReenrtantLock עבור סִנכְּרוּן:

מחלקה ציבורית SharedObject {// ... נעילת ReentrantLock = ReentrantLock חדש (); מונה int = 0; חלל ציבורי לבצע () {lock.lock (); נסה את {// הסעיף הקריטי כאן לספור ++; } סוף סוף {lock.unlock (); }} // ...}

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

בוא נראה איך ה tryLock () עובד:

חלל ציבורי performTryLock () {// ... בוליאני isLockAcquired = lock.tryLock (1, TimeUnit.SECONDS); אם (isLockAcquired) {נסה {// הקטע הקריטי כאן} סוף סוף {lock.unlock (); }} // ...} 

במקרה זה, החוט קורא tryLock (), ימתין לשנייה אחת ויתור על ההמתנה אם הנעילה אינה זמינה.

4.2. ReentrantReadWriteLock

ReentrantReadWriteLock הכיתה מיישמת את ReadWriteLock מִמְשָׁק.

בואו נראה כללים לרכישת ה- ReadLock אוֹ כתיבת נעילה על ידי חוט:

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

בואו נראה כיצד להשתמש ב- ReadWriteLock:

מחלקה ציבורית SynchronizedHashMapWithReadWriteLock {Map syncHashMap = HashMap חדש (); ReadWriteLock lock = ReentrantReadWriteLock חדש (); // ... נעילת writeLock = lock.writeLock (); put void public (String key, String value) {try {writeLock.lock (); syncHashMap.put (מפתח, ערך); } סוף סוף {writeLock.unlock (); }} ... הסרת מחרוזת ציבורית (מפתח מחרוזת) {נסה {writeLock.lock (); להחזיר syncHashMap.remove (מפתח); } סוף סוף {writeLock.unlock (); }} // ...}

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

נעל readLock = lock.readLock (); // ... ציבורי מחרוזת לקבל (מפתח מחרוזת) {נסה {readLock.lock (); להחזיר syncHashMap.get (מפתח); } סוף סוף {readLock.unlock (); }} ציבורי בוליאני containKey (מפתח מחרוזת) {נסה {readLock.lock (); להחזיר syncHashMap.containsKey (מפתח); } סוף סוף {readLock.unlock (); }}

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

4.3. StampedLock

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

מחלקה ציבורית StampedLockDemo {Map map = HashMap חדש (); נעילת StampedLock פרטית = StampedLock חדש (); put void put (String key, String value) {long stamp = lock.writeLock (); נסה את {map.put (מפתח, ערך); } סוף סוף {lock.unlockWrite (חותמת); }} ציבורי מחרוזת מקבל (מפתח מחרוזת) זורק את InterruptedException {חותמת ארוכה = lock.readLock (); נסה {return map.get (key); } סוף סוף {lock.unlockRead (חותמת); }}}

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

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

מחרוזת ציבורית readWithOptimisticLock (מפתח מחרוזת) {חותמת ארוכה = lock.tryOptimisticRead (); ערך מחרוזת = map.get (מפתח); אם (! lock.validate (stamp)) {stamp = lock.readLock (); נסה {return map.get (key); } סוף סוף {lock.unlock (חותמת); } } ערך החזרה; }

5. עבודה עם תנאים

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

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

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

מחלקה ציבורית ReentrantLockWithCondition {מחסנית מחסנית = מחסנית חדשה (); int CAPACITY = 5; נעילת ReentrantLock = ReentrantLock חדש (); מצב stackEmptyCondition = lock.newCondition (); מצב stackFullCondition = lock.newCondition (); pushToStack בטל פומבי (פריט מחרוזת) {נסה {lock.lock (); בעוד (stack.size () == CAPACITY) {stackFullCondition.await (); } stack.push (פריט); stackEmptyCondition.signalAll (); } סוף סוף {lock.unlock (); }} מחרוזת ציבורית popFromStack () {נסה {lock.lock (); בעוד (stack.size () == 0) {stackEmptyCondition.await (); } להחזיר stack.pop (); } סוף סוף {stackFullCondition.signalAll (); lock.unlock (); }}}

6. מסקנה

במאמר זה ראינו יישומים שונים של ה- לנעול ממשק והממשק שהוצג לאחרונה StampedLock מעמד. בדקנו גם כיצד אנו יכולים להשתמש ב- מַצָב בכיתה לעבודה עם מספר תנאים.

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


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