JavaRush /בלוג Java /Random-HE /רמה 26. תשובות לשאלות ראיון בנושא הרמה. חלק 2. שאלות 6-9,...
zor07
רָמָה
Санкт-Петербург

רמה 26. תשובות לשאלות ראיון בנושא הרמה. חלק 2. שאלות 6-9, 11-12

פורסם בקבוצה
רמה 26. תשובות לשאלות ראיון בנושא הרמה.  חלק 2. שאלות 6-9, 11-12 - 1

6. מהו קנקרנצי?

Concurrency היא ספריית מחלקות ב-Java המכילה מחלקות מיוחדות המותאמות לעבודה על פני שרשורים מרובים. שיעורים אלו נאספים בחבילה java.util.concurrent. ניתן לחלק אותם באופן סכמטי לפי פונקציונליות באופן הבא: רמה 26. תשובות לשאלות ראיון בנושא הרמה.  חלק 2. שאלות 6-9, 11-12 - 2Concurrent Collections - אוסף של אוספים שעובדים ביעילות רבה יותר בסביבה מרובת הליכי הליכי מאשר אוספים אוניברסליים סטנדרטיים מהחבילה java.util. במקום מעטפת בסיסית Collections.synchronizedListעם חסימת גישה לכל האוסף, נעשה שימוש במנעולים על מקטעי נתונים, או שהעבודה מותאמת לקריאה מקבילה של נתונים באמצעות אלגוריתמים ללא המתנה. תורים - תורים לא חוסמים וחסימת תורים עם תמיכה בריבוי השחלות. תורים לא חוסמים מיועדים למהירות ותפעול ללא חסימת חוטים. תורי חסימה משמשים כאשר אתה צריך "להאט" את שרשורי "המפיק" או "הצרכן" אם תנאים מסוימים אינם מתקיימים, למשל, התור ריק או על גדותיו, או שאין "צרכן" פנוי. סנכרון הם כלי עזר לסנכרון שרשורים. הם נשק רב עוצמה במחשוב "מקביל". מבצעים - מכיל מסגרות מצוינות ליצירת מאגר שרשורים, תזמון משימות אסינכרוניות והשגת תוצאות. מנעולים - מייצג מנגנוני סנכרון חוטים חלופיים וגמישים יותר בהשוואה synchronizedלבסיסיים wait. אטומיקה - שיעורים עם תמיכה בפעולות אטומיות על פרימיטיביות והתייחסויות. מָקוֹר:notifynotifyAll

7. אילו שיעורים מקנקרנסי אתה מכיר?

התשובה לשאלה זו מצוינת בצורה מושלמת במאמר זה . אני לא רואה טעם להדפיס את כל זה כאן, אז אתן תיאורים רק של אותם שיעורים שהיה לי הכבוד להכיר את עצמי בקצרה. ConcurrentHashMap<K, V> - בניגוד Hashtableלחסימות synhronizedב- HashMap, הנתונים מוצגים בצורה של קטעים, מחולקים ל-hashs של מפתחות. כתוצאה מכך, הגישה לנתונים מתבצעת על ידי מקטעים ולא על ידי אובייקט בודד. בנוסף, איטרטורים מייצגים נתונים לתקופת זמן מסוימת ואינם זורקים ConcurrentModificationException. AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray - מה אם בכיתה אתה צריך לסנכרן גישה למשתנה פשוט אחד מסוג int? אתה יכול להשתמש במבנים עם synchronized, וכאשר משתמשים בפעולות אטומיות set/get, volatile. אבל אתה יכול לעשות אפילו טוב יותר על ידי שימוש בשיעורים חדשים Atomic*. עקב השימוש ב-CAS, הפעולות עם מחלקות אלו מהירות יותר מאשר אם הן מסונכרנות באמצעות synchronized/volatile. בנוסף, ישנן שיטות לתוספת אטומית בכמות נתונה, כמו גם להגדלה/הפחתה.

8. כיצד פועלת הכיתה ConcurrentHashMap?

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

    בניגוד לאלמנטים HashMap, Entryב- ConcurrentHashMapמוצהרים כ- volatile. זוהי תכונה חשובה, גם עקב שינויים ב- JMM .

    static final class HashEntry<K, V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K, V> next;
    
        HashEntry(K key, int hash, HashEntry<K, V> next, V value) {
            this .key = key;
            this .hash = hash;
            this .next = next;
            this .value = value;
         }
    
        @SuppressWarnings("unchecked")
        static final <K, V> HashEntry<K, V>[] newArray(int i) {
            return new HashEntry[i];
        }
    }
  2. פונקציית Hash

    ConcurrentHashMapנעשה שימוש גם בפונקציית hashing משופרת.

    תן לי להזכיר לך איך זה היה ב- HashMapJDK 1.2:

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    גרסה מ-ConcurrentHashMap JDK 1.5:

    private static int hash(int h) {
        h += (h << 15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h << 3);
        h ^= (h >>> 6);
        h += (h << 2) + (h << 14);
        return h ^ (h >>> 16);
    }

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

  3. פלחים

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

  4. רמת במקביל

    פרמטר זה משפיע על השימוש בכרטיס הזיכרון ועל מספר המקטעים בכרטיס.

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

  5. סה"כ

    אז, היתרונות העיקריים ותכונות היישום ConcurrentHashMap:

    • hashmapלמפה יש ממשק אינטראקציה דומה לזה
    • פעולות הקריאה אינן מצריכות נעילה ומתבצעות במקביל
    • לעיתים קרובות ניתן לבצע גם פעולות כתיבה במקביל ללא חסימה
    • בעת יצירה, מצוין הדרוש concurrencyLevel, שנקבע על ידי קריאה וכתיבה סטטיסטיקות
    • לרכיבי המפה יש ערך valueהמוצהר כvolatile
    מקור: How ConcurrentHashMap Works

9. מהו מחלקת ה- Lock?

כדי לשלוט בגישה למשאב משותף, נוכל להשתמש במנעולים כחלופה למפעיל המסונכרן. פונקציונליות הנעילה ארוזה ב- java.util.concurrent.locks. ראשית, השרשור מנסה לגשת למשאב המשותף. אם זה פנוי, אז מנעול מונח על החוט. לאחר השלמת העבודה, נעילת המשאב המשותף משוחררת. אם המשאב אינו פנוי וכבר מוצב עליו מנעול, השרשור ממתין עד לשחרור המנעול הזה. מחלקות נעילה מיישמות ממשק Lockהמגדיר את השיטות הבאות:
  • void lock():ממתין עד לרכישת המנעול
  • boolean tryLock():מנסה להשיג מנעול; אם המנעול מתקבל, הוא מחזיר אמת . אם המנעול לא נרכש, הוא מחזיר false . בניגוד לשיטה, lock()היא לא מחכה לרכוש מנעול אם לא זמין
  • void unlock():מסיר את המנעול
  • Condition newCondition():מחזירה את האובייקט Conditionהמשויך למנעול הנוכחי
ארגון הנעילה במקרה הכללי הוא די פשוט: להשיג את המנעול, השיטה נקראת lock(), ולאחר סיום העבודה עם משאבים משותפים, השיטה נקראת unlock(), המשחררת את המנעול. האובייקט Conditionמאפשר לך לנהל חסימה. ככלל, כדי לעבוד עם מנעולים, נעשה שימוש במחלקה ReentrantLockמהחבילה . java.util.concurrent.locks.מחלקה זו מיישמת את הממשק Lock. בואו נסתכל על השימוש ב-Java Lock API באמצעות תוכנה קטנה כדוגמה: אז, נניח שיש לנו מחלקה Resourceעם כמה שיטות ושיטות בטוחות בשרשור שבהן לא נדרשת בטיחות שרשור.
public class Resource {

    public void doSomething(){
        // пусть здесь происходит работа с базой данных
    }

    public void doLogging(){
        // потокобезопасность для логгирования нам не требуется
    }
}
כעת ניקח מחלקה המיישמת את הממשק Runnableומשתמשת בשיטות מחלקות Resource.
public class SynchronizedLockExample implements Runnable{

    // экземпляр класса Resource для работы с методами
    private Resource resource;

    public SynchronizedLockExample(Resource r){
        this.resource = r;
    }

    @Override
    public void run() {
        synchronized (resource) {
            resource.doSomething();
        }
        resource.doLogging();
    }
}
כעת נכתוב מחדש את התוכנית לעיל באמצעות ה-API של Lock במקום ה- synchronized.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{

    private Resource resource;
    private Lock lock;

    public ConcurrencyLockExample(Resource r){
        this.resource = r;
        this.lock = new ReentrantLock();
    }

    @Override
    public void run() {
        try {
            // лочим на 10 секунд
            if(lock.tryLock(10, TimeUnit.SECONDS)){
            resource.doSomething();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //убираем лок
            lock.unlock();
        }
        // Для логгирования не требуется потокобезопасность
        resource.doLogging();
    }

}
כפי שניתן לראות מהתוכנית, אנו משתמשים בשיטה tryLock()כדי לוודא שהשרשור ממתין רק לפרק זמן מסוים. אם הוא לא משיג מנעול על החפץ, הוא פשוט מתחבר ויוצא. עוד נקודה חשובה. עליך להשתמש בבלוק try-finallyכדי להבטיח שהמנעול ישוחרר גם אם השיטה doSomething()זורקת חריגה. מקורות:

11. מהו מוטקס?

mutex הוא אובייקט מיוחד לסנכרון שרשורים/תהליכים. זה יכול לקחת שתי מדינות - עסוק ופנוי. כדי לפשט, mutex הוא משתנה בוליאני שלוקח שני ערכים: busy (true) ו-free (false). כאשר חוט רוצה בעלות בלעדית על אובייקט, הוא מסמן את המוטקס שלו כעסוק, וכאשר הוא מסיים לעבוד איתו, הוא מסמן את המוטקס שלו כחופשי. מוטקס מחובר לכל אובייקט ב-Java. רק למכשיר Java יש גישה ישירה ל-mutex. זה מוסתר מהמתכנת.

12. מהו מוניטור?

מוניטור הוא מנגנון מיוחד (חתיכת קוד) - תוסף מעל המוטקס, המבטיח פעולה נכונה איתו. אחרי הכל, לא מספיק לסמן שהאובייקט תפוס, עלינו גם לוודא ששרשורים אחרים לא ינסו להשתמש באובייקט התפוס. ב-Java, המוניטור מיושם באמצעות מילת המפתח synchronized. כאשר אנו כותבים בלוק מסונכרן, מהדר Java מחליף אותו בשלוש פיסות קוד:
  1. בתחילת הבלוק synchronizedמתווסף קוד שמסמן את המוטקס כעסוק.
  2. בסוף הבלוק synchronizedמתווסף קוד המסמן את המוטקס כחופשי.
  3. לפני החסימה synchronizedמתווסף קוד שבודק אם ה-mutex תפוס, ואז על השרשור לחכות לשחרורו.
חלק 1
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION