ConcurrentHashMap的讀操作不需要加鎖

ConcurrentHashMap是線程安全的,在JDK1.7中是使用ReentrantLock來保證線程安全,在JDK1.8中,它是使用synchronized進行加鎖保證線程安全的;
但是concurrentHashMap中的get操作是沒有加鎖的,那麼它是如何來保證線程安全的呢?
concurrentHashMap中的get源碼:

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

通過觀察源碼可知,concurrentHashMap中的get操作沒有加鎖,它是如何保證線程安全,保證讀到的數據不是髒數據呢?此時,我們就要先說一說volatile這個關鍵字了:
在說volatile之前,讓我們先來了解下java內存模型:
在這裏插入圖片描述
Java內存模型規定了所有的變量都存儲在主內存中,每條線程都有自己的工作內存,線程的工作內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在各自的工作內存中進行,而不能直接讀寫主內存中的變量。不同的線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成,而工作內存中的變量何時重寫入主內存,又不是我們能確定的;

普通的共享變量是不能保證可見性的,因爲普通變量在某一個線程的工作內存中被修改後,什麼時候被寫入主內存是不確定的,當其他線程去讀取主內存變量時,此時主內存內的值可能是原來的舊值;
但是如果使用了volatile來修飾該變量,其會強制將修改的值立即寫入主內存,而且爲了保證各個處理器的工作內存是一致的,還會實現緩存一致性協議:當某個線程在寫數據時,如果發現被操作的變量是共享變量,cpu則會通知其他線程該變量的緩存行是無效的,因此其他線程在讀取該變量時會發現其變量無效而去主內存中加載數據;

而ConcurrentHashMap的get操作無鎖就是因爲其操作的Node和next都是由volatile來修飾的:

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章