深入理解ConcurrentHashmap(JDK1.6到1.7)

concurrentHashmap是JDK提供的一個(gè)線程安全的Map容器類质和,因?yàn)樗蔷€程安全的城丧,同時(shí)獲取和釋放鎖的代價(jià)很低奥裸,所以被廣泛的應(yīng)用在各種場(chǎng)景下皆愉。在開(kāi)源項(xiàng)目中隨處可見(jiàn)。對(duì)于concurrentHashmap腋妙,以前都是只會(huì)用默怨,但是從來(lái)沒(méi)有深入了解和學(xué)習(xí),最近抽出時(shí)間分析一番骤素。ps:對(duì)于concurrentHashmap匙睹,JDK1.6和JDK1.7的實(shí)現(xiàn)是不一樣的,這里主要以JDK1.7的分析為主谆甜。

concurrentHashmap和HashMap的區(qū)別:#####

concurrentHashmap和HashMap大多數(shù)下的使用場(chǎng)景基本一致垃僚,但最大的區(qū)別就是concurrentHashmap是線程安全的HashMap則不是,在并發(fā)的場(chǎng)景下HashMap存在死循環(huán)的問(wèn)題规辱。具體的成因,我會(huì)總結(jié)一篇這樣的筆記栽燕。

concurrentHashmap和HashTable的區(qū)別:#####

HashTable是一個(gè)線程安全的容器類罕袋,在HashTable所有方法都是用synchronized關(guān)鍵字修飾的改淑,也就是說(shuō)它是線程安全的。但是HashTable的性能十分低下浴讯,對(duì)于每一個(gè)操作都要做家鎖操作朵夏,即使操作的是不同的bucket內(nèi)的Entry也要全局枷鎖,在高并發(fā)場(chǎng)景下性能低下榆纽。而concurrentHashmap引入了分段鎖的概念仰猖,對(duì)于不同Bucket的操作不需要全局鎖來(lái)保證線程安全。

concurrentHashmap在JDK1.6和JDK1.7的實(shí)現(xiàn)異同點(diǎn):#####

在學(xué)習(xí)源碼之前奈籽,我也看了很多博客饥侵,發(fā)現(xiàn)上面說(shuō)法不一,后來(lái)對(duì)比了代碼才知道衣屏,原來(lái)JDK1.7將concurrentHashmap的實(shí)現(xiàn)機(jī)制改變了躏升,但是代碼確實(shí)比原來(lái)好懂了一下。

初始化:#####
    /**
     * The default initial capacity for this table,
     * used when not otherwise specified in a constructor.
     * 默認(rèn)的初始化容量
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * The default load factor for this table, used when not
     * otherwise specified in a constructor.
     * 默認(rèn)負(fù)載因子
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The default concurrency level for this table, used when not
     * otherwise specified in a constructor.
     * 默認(rèn)的并發(fā)等級(jí)
     */
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    /**
     * The maximum capacity, used if a higher value is implicitly
     * specified by either of the constructors with arguments.  MUST
     * be a power of two <= 1<<30 to ensure that entries are indexable
     * using ints.
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The minimum capacity for per-segment tables.  Must be a power
     * of two, at least two to avoid immediate resizing on next use
     * after lazy construction.
     * 一個(gè)Segment中Table數(shù)組最小長(zhǎng)度為2
     */
    static final int MIN_SEGMENT_TABLE_CAPACITY = 2;

    /**
     * The maximum number of segments to allow; used to bound
     * constructor arguments. Must be power of two less than 1 << 24.
     * Segment的最大數(shù)
     */
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/**
     * Creates a new, empty map with the specified initial
     * capacity, load factor and concurrency level.
     *
     * @param initialCapacity the initial capacity. The implementation
     * performs internal sizing to accommodate this many elements.
     * @param loadFactor  the load factor threshold, used to control resizing.
     * Resizing may be performed when the average number of elements per
     * bin exceeds this threshold.
     * @param concurrencyLevel the estimated number of concurrently
     * updating threads. The implementation performs internal sizing
     * to try to accommodate this many threads.
     * @throws IllegalArgumentException if the initial capacity is
     * negative or the load factor or concurrencyLevel are
     * nonpositive.
     */
    @SuppressWarnings("unchecked")
    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        //首先檢查入?yún)⒌挠行?        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        //限制并發(fā)度
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        //Segment的段尋址的因子
        int sshift = 0;
        //Segments數(shù)組的長(zhǎng)度
        int ssize = 1;
        //根據(jù)并發(fā)等級(jí)來(lái)確定Segment的數(shù)組長(zhǎng)度和段段尋址的因子
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        //默認(rèn)并發(fā)等級(jí)下ssize為16狼忱,sshift為4膨疏,這里有一個(gè)關(guān)系就是2的sshift次方等于ssize,主要是為了計(jì)算段的位置
        //segmentShift為Segment尋址的偏移量
        this.segmentShift = 32 - sshift;
        //Segment掩碼钻弄,ssize為16時(shí)佃却,segmentMask為0xFF
        this.segmentMask = ssize - 1;
        //判斷初始化容量的有效性
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //計(jì)算一個(gè)Segment的容量
        int c = initialCapacity / ssize;
        //保證容量足夠。ps: /是整除窘俺,所以需要通過(guò)下面語(yǔ)句保證
        if (c * ssize < initialCapacity)
            ++c;
        //計(jì)算Segment中的table容量饲帅,最小為2,如果小于c,那么x2
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        //創(chuàng)建一個(gè)Segment0批销,以后以此為鏡像洒闸,新建Segment
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);
        //創(chuàng)建Segment數(shù)組,長(zhǎng)度為ssize
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
        //用UNSAFE的方法將S0放到ss[0],相當(dāng)于初始化ss
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

ConcurrentHashmap的結(jié)構(gòu)圖:#####
Paste_Image.png
元素定位:#####

初始化之后均芽,我們需要看看concurrentHashmap是怎么定位元素的丘逸,比較關(guān)鍵的是hash算法。

/**
     * Applies a supplemental hash function to a given hashCode, which
     * defends against poor quality hash functions.  This is critical
     * because ConcurrentHashMap uses power-of-two length hash tables,
     * that otherwise encounter collisions for hashCodes that do not
     * differ in lower or upper bits.
     */
    private int hash(Object k) {
        int h = hashSeed;

        if ((0 != h) && (k instanceof String)) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // Spread bits to regularize both segment and index locations,
        // using variant of single-word Wang/Jenkins hash.
        h += (h <<  15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h <<   3);
        h ^= (h >>>  6);
        h += (h <<   2) + (h << 14);
        return h ^ (h >>> 16);
    }

看到這里掀宋,絕大多數(shù)人都和我一樣是懵逼的深纲,的確我現(xiàn)在也沒(méi)弄明白是什么邏輯,但是這里有一個(gè)疑問(wèn)劲妙,就是Object本身是有hashcode湃鹊,那么為什么不用Object的HashCode呢?看過(guò)《算法導(dǎo)論》的人應(yīng)該明白,這種算法可能是有問(wèn)題的镣奋,那就是在hash取模的時(shí)候币呵,主要是根據(jù)后幾位確定取模之后的index,所以會(huì)很不均勻侨颈。所以需要重新設(shè)計(jì)hash算法余赢。

put的實(shí)現(xiàn):#####

在了解了重新設(shè)計(jì)的Hashcode之后芯义,我們需要知道是怎么根據(jù)hash定位到Segment和Segment里面table的索引。那么我們通過(guò)學(xué)習(xí)put方法妻柒,附帶看一下元素定位的規(guī)則:

 /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * <p> The value can be retrieved by calling the <tt>get</tt> method
     * with a key that is equal to the original key.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>
     * @throws NullPointerException if the specified key or value is null
     */
    @SuppressWarnings("unchecked")
    public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        //定位Segment扛拨,讓Hash右移動(dòng)segmentShift位,默認(rèn)情況下就是28位(總長(zhǎng)32位)举塔,之后和segmentMask(0XFF)做與操作绑警,得到段的索引
        int j = (hash >>> segmentShift) & segmentMask;
        //利用UNSAFE.getObject中的方法獲取到目標(biāo)的Segment。
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            //如果沒(méi)有取到目標(biāo)Segment,所以需要保證能取到這個(gè)Segment,沒(méi)有的話創(chuàng)建一個(gè)Segment
            s = ensureSegment(j);
        //代理到Segment的put方法
        return s.put(key, hash, value, false);
    }

上面的代碼中其實(shí)是有一些點(diǎn)比較難理解遗遵,首先是(Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)),
UNSAFE這種用法是在JDK1.6中沒(méi)有的,主要是利用Native方法來(lái)快速的定位元素章郁。看下SSHIFT和SBASE志衍。

// Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long SBASE;
    private static final int SSHIFT;
    private static final long TBASE;
    private static final int TSHIFT;
    private static final long HASHSEED_OFFSET;

    static {
        int ss, ts;
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class tc = HashEntry[].class;
            Class sc = Segment[].class;
            TBASE = UNSAFE.arrayBaseOffset(tc);
            SBASE = UNSAFE.arrayBaseOffset(sc);
            ts = UNSAFE.arrayIndexScale(tc);
            ss = UNSAFE.arrayIndexScale(sc);
            HASHSEED_OFFSET = UNSAFE.objectFieldOffset(
                ConcurrentHashMap.class.getDeclaredField("hashSeed"));
        } catch (Exception e) {
            throw new Error(e);
        }
        if ((ss & (ss-1)) != 0 || (ts & (ts-1)) != 0)
            throw new Error("data type scale not a power of two");
        SSHIFT = 31 - Integer.numberOfLeadingZeros(ss);
        TSHIFT = 31 - Integer.numberOfLeadingZeros(ts);
    }

這里我是有一些迷惑的,SBASE是基址暖庄,但是SSHIFT是什么其實(shí)我是不理解的,但是猜測(cè)應(yīng)該是一種計(jì)算偏移量的方式(ps:如果有明白的大神楼肪,請(qǐng)留言我)培廓。這樣就獲得了指定索引的Segment。
還有一個(gè)點(diǎn)是:ensureSegment()

    /**
     * Returns the segment for the given index, creating it and
     * recording in segment table (via CAS) if not already present.
     *
     * @param k the index
     * @return the segment
     */
    @SuppressWarnings("unchecked")
    private Segment<K,V> ensureSegment(int k) {
        final Segment<K,V>[] ss = this.segments;
        long u = (k << SSHIFT) + SBASE; // raw offset
        Segment<K,V> seg;
        //getObjectVolatile是以Volatile的方式獲得目標(biāo)的Segment春叫,Volatile是為了保證可見(jiàn)性肩钠。
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
            //如果沒(méi)有取到,那么證明指定的Segment不存在暂殖,那么需要新建Segment,方式是以ss[0]為鏡像創(chuàng)建价匠。
            Segment<K,V> proto = ss[0]; // use segment 0 as prototype
            int cap = proto.table.length;
            float lf = proto.loadFactor;
            int threshold = (int)(cap * lf);
            HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
            if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                == null) { // 再次檢查
                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);//創(chuàng)建新Segment
                //以CAS的方式,將新建的Segment呛每,set到指定的位置踩窖。
                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                       == null) {
                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                        break;
                }
            }
        }
        return seg;
    }

上面的代碼就是保證,在put之前晨横,要保證目標(biāo)的Segment是存在的洋腮,不存在需要?jiǎng)?chuàng)建一個(gè)Segment。
put方法代理到了Segment的put方法手形,Segment extends 了ReentrantLock啥供,以至于它能當(dāng)做一個(gè)Lock使用。那么我們看一下Segment的put的實(shí)現(xiàn):

        final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            //因?yàn)閜ut操作會(huì)改變整體的結(jié)構(gòu)库糠,所以需要保證段的線程安全性伙狐,所以首先tryLock
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                //新建tab引用,避免直接引用Volatile導(dǎo)致性能損耗,
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                //Volatile讀鳞骤,保證可見(jiàn)性
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        //遍歷HashEntry數(shù)組窒百,尋找可替換的HashEntry
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        //如果不存在可替換的HashEntry黍判,如果在scanAndLockForPut中建立了此Node直接SetNext豫尽,追加到鏈表頭
                        if (node != null)
                            node.setNext(first);
                        else
                            //如果沒(méi)有則新建一個(gè)Node,添加到鏈表頭
                            node = new HashEntry<K,V>(hash, key, value, first);
                        //容量計(jì)數(shù)+1
                        int c = count + 1;
                        //如果容量不足顷帖,那么擴(kuò)容
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            //以Volatile寫(xiě)的方式美旧,替換tab[index]的引用
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

put方法是做了加鎖操作的,所以不用過(guò)多的考慮線程安全的問(wèn)題贬墩,但是get操作為了保證性能是沒(méi)有加鎖的榴嗅,所以需要盡量的保證數(shù)據(jù)的可見(jiàn)性,能讓get得到最新的數(shù)據(jù)陶舞。上面的方法里有一點(diǎn)是比較難理解的:
1.scanAndLockForPut(key, hash, value)在做什么:

/**
         * Scans for a node containing given key while trying to
         * acquire lock, creating and returning one if not found. Upon
         * return, guarantees that lock is held. UNlike in most
         * methods, calls to method equals are not screened: Since
         * traversal speed doesn't matter, we might as well help warm
         * up the associated code and accesses as well.
         *
         * @return a new node if key not found, else null
         */
        private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
            HashEntry<K,V> first = entryForHash(this, hash);
            HashEntry<K,V> e = first;
            HashEntry<K,V> node = null;
            int retries = -1; // negative while locating node
            while (!tryLock()) {
                HashEntry<K,V> f; // to recheck first below
                if (retries < 0) {
                    if (e == null) {
                        if (node == null) // speculatively create node
                            node = new HashEntry<K,V>(hash, key, value, null);
                        retries = 0;
                    }
                    else if (key.equals(e.key))
                        retries = 0;
                    else
                        e = e.next;
                }
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                else if ((retries & 1) == 0 &&
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f; // re-traverse if entry changed
                    retries = -1;
                }
            }
            return node;
        }

從上面的邏輯可以看出來(lái)嗽测,其實(shí)就是在獲取鎖的時(shí)候順便檢查一下指定index的HashEntry有沒(méi)有變化,同時(shí)如果目標(biāo)節(jié)點(diǎn)不存在創(chuàng)建一個(gè)新的目標(biāo)節(jié)點(diǎn)肿孵。但是為什么做這樣的檢查唠粥,查了很多資料結(jié)合注釋理解是,為了事先做數(shù)據(jù)的緩存停做,讓這些數(shù)據(jù)緩存在CPU的cache中晤愧,這樣后續(xù)在使用時(shí)能避免Cache missing。ps:scanAndLockForPut有個(gè)孿生兄弟scanAndLock蛉腌,作用都差不多官份。

和JDK1.6的實(shí)現(xiàn)的不同:

1. V put(K key, int hash, V value, boolean onlyIfAbsent) {  
2.     lock();  
3.     try {  
4.         int c = count;  
5.         if (c++ > threshold) // ensure capacity  
6.             rehash();  
7.         HashEntry<K,V>[] tab = table;  
8.         int index = hash & (tab.length - 1);  
9.         HashEntry<K,V> first = tab[index];  
10.         HashEntry<K,V> e = first;  
11.         while (e != null && (e.hash != hash || !key.equals(e.key)))  
12.             e = e.next;  
13.   
14.         V oldValue;  
15.         if (e != null) {  
16.             oldValue = e.value;  
17.             if (!onlyIfAbsent)  
18.                 e.value = value;  
19.         }  
20.         else {  
21.             oldValue = null;  
22.             ++modCount;  
23.             tab[index] = new HashEntry<K,V>(key, hash, first, value);  
24.             count = c; // write-volatile  
25.         }  
26.         return oldValue;  
27.     } finally {  
28.         unlock();  
29.     }  
30. }

JDK1.6的實(shí)現(xiàn)和JDK1.7的實(shí)現(xiàn)比較相似,但是主要區(qū)別是烙丛,沒(méi)有使用一些UNSAFE的方法去保證內(nèi)存的可見(jiàn)性舅巷,而是通過(guò)一個(gè)Volatile變量——count去實(shí)現(xiàn)。在開(kāi)始的時(shí)候讀count保證lock的內(nèi)存語(yǔ)意河咽,最后寫(xiě)count實(shí)現(xiàn)unlock的內(nèi)存語(yǔ)意钠右。
但是這里存在一個(gè)問(wèn)題,new HashEntry操作存在重排序問(wèn)題库北,導(dǎo)致在getValue的時(shí)候tab[index]不為null爬舰,但是value為null。

get方法:#####

看過(guò)了put方法之后寒瓦,接下來(lái)我們看比較關(guān)鍵的方法get():

  /**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code key.equals(k)},
     * then this method returns {@code v}; otherwise it returns
     * {@code null}.  (There can be at most one such mapping.)
     *
     * @throws NullPointerException if the specified key is null
     */
    public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

可以看出來(lái)情屹,get方法很簡(jiǎn)單,同時(shí)get是沒(méi)有加鎖的杂腰,那么get是如何保證可見(jiàn)性的呢垃你?首先獲取指定index的Segment,利用getObjectVolatile獲取指定index的first HashEntry,之后遍歷HashEntry鏈表惜颇,這里比較關(guān)鍵的是HashEntry的數(shù)據(jù)結(jié)構(gòu):

volatile V value;
volatile HashEntry<K,V> next;

兩個(gè)變量是volatile的皆刺,也就是說(shuō),兩個(gè)變量的讀寫(xiě)能保證數(shù)據(jù)的可見(jiàn)性凌摄。
所以在變量HashEntry時(shí)羡蛾,總能保證得到最新的值。

JKD1.6的get方法的實(shí)現(xiàn):

1. V get(Object key, int hash) { 
2. if (count != 0) { // read-volatile 當(dāng)前桶的數(shù)據(jù)個(gè)數(shù)是否為0
3. HashEntry<K,V> e = getFirst(hash); 得到頭節(jié)點(diǎn)
4. while (e != null) { 
5. if (e.hash == hash && key.equals(e.key)) { 
6. V v = e.value; 
7. if (v != null) 
8. return v; 
9. return readValueUnderLock(e); // recheck 
10. } 
11. e = e.next;
12. } 
13. }
14. return null; 
15. } 

首先是讀取count變量锨亏,因?yàn)閮?nèi)存的可見(jiàn)性痴怨,總是能返回最新的結(jié)構(gòu),但是對(duì)于getFirst可能得到的是過(guò)時(shí)的HashEntry器予。接下來(lái)獲取到HashEntry之后getValue浪藻。但是這里為什么要做一個(gè)value的判空,原因就是上一步put的重排序問(wèn)題乾翔,如果為null爱葵,那么只能加鎖,加鎖之后進(jìn)行重新讀取反浓。但是這樣確實(shí)會(huì)帶來(lái)一些開(kāi)銷萌丈。

為什么JDK1.6的實(shí)現(xiàn)是弱一致性的?#####

這里比較重要的一點(diǎn)就是勾习,為什么JDK1.6的是弱一致性的浓瞪?因?yàn)镴DK1.6的所有可見(jiàn)性都是以count實(shí)現(xiàn)的,當(dāng)put和get并發(fā)時(shí)巧婶,get可能獲取不到最新的結(jié)果乾颁,這就是JDK1.6中ConcurrentHashMap弱一致性問(wèn)題,主要問(wèn)題是 tab[index] = new HashEntry<K,V>(key, hash, first, value);不一定 happened before getFirst(hash)艺栈;盜圖一張:

Paste_Image.png

而JDK1.7的實(shí)現(xiàn)英岭,對(duì)于每一個(gè)操作都是Volatile變量的操作,能保證線程之間的可見(jiàn)性湿右,所以不存在弱一致性的問(wèn)題诅妹。

remove方法:#####

看了put方法之后,接下來(lái)看一下同樣能改變結(jié)構(gòu)的remove方法:

/**
     * Removes the key (and its corresponding value) from this map.
     * This method does nothing if the key is not in the map.
     *
     * @param  key the key that needs to be removed
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>
     * @throws NullPointerException if the specified key is null
     */
    public V remove(Object key) {
        int hash = hash(key);
        Segment<K,V> s = segmentForHash(hash);
        return s == null ? null : s.remove(key, hash, null);
    }
        final V remove(Object key, int hash, Object value) {
            if (!tryLock())
                scanAndLock(key, hash);
            V oldValue = null;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> e = entryAt(tab, index);
                HashEntry<K,V> pred = null;
                while (e != null) {
                    K k;
                    HashEntry<K,V> next = e.next;
                    if ((k = e.key) == key ||
                        (e.hash == hash && key.equals(k))) {
                        V v = e.value;
                        if (value == null || value == v || value.equals(v)) {
                            if (pred == null)
                                setEntryAt(tab, index, next);
                            else
                                pred.setNext(next);
                            ++modCount;
                            --count;
                            oldValue = v;
                        }
                        break;
                    }
                    pred = e;
                    e = next;
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

remove方法毅人,同樣是代理到Segment的remove吭狡,在這里調(diào)用了scanAndLock方法,這個(gè)在前面已經(jīng)說(shuō)過(guò)了丈莺。這里的remove邏輯是比較簡(jiǎn)單的就不贅述了划煮。

size方法:#####

接下來(lái)看最后一個(gè)方法,也是一個(gè)跨Segment的方法:

/**
     * Returns the number of key-value mappings in this map.  If the
     * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
     * <tt>Integer.MAX_VALUE</tt>.
     *
     * @return the number of key-value mappings in this map
     */
    public int size() {
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        final Segment<K,V>[] segments = this.segments;
        int size;
        boolean overflow; // true if size overflows 32 bits
        long sum;         // sum of modCounts
        long last = 0L;   // previous sum
        int retries = -1; // first iteration isn't retry
        try {
            for (;;) {
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }
                sum = 0L;
                size = 0;
                overflow = false;
                for (int j = 0; j < segments.length; ++j) {
                    Segment<K,V> seg = segmentAt(segments, j);
                    if (seg != null) {
                        sum += seg.modCount;
                        int c = seg.count;
                        if (c < 0 || (size += c) < 0)
                            overflow = true;
                    }
                }
                if (sum == last)
                    break;
                last = sum;
            }
        } finally {
            if (retries > RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    segmentAt(segments, j).unlock();
            }
        }
        return overflow ? Integer.MAX_VALUE : size;
    }

size是一個(gè)跨Segment的操作缔俄,所以避免不了多個(gè)鎖的獲取弛秋,這里主要是通過(guò)如下方法進(jìn)行所有鎖的獲绕黪铩:

 if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }

獲取所有鎖之后,對(duì)每一個(gè)Segment的size獲取蟹略,最后相加返回登失。

參考鏈接:#####

為什么ConcurrentHashMap是弱一致的
Under The Covers Of Concurrent Hash Map
Java集合---ConcurrentHashMap原理分析
Java Core系列之ConcurrentHashMap實(shí)現(xiàn)(JDK 1.7)
探索 ConcurrentHashMap 高并發(fā)性的實(shí)現(xiàn)機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市挖炬,隨后出現(xiàn)的幾起案子揽浙,更是在濱河造成了極大的恐慌,老刑警劉巖茅茂,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捏萍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡空闲,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)走敌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碴倾,“玉大人,你說(shuō)我怎么就攤上這事掉丽〉疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵捶障,是天一觀的道長(zhǎng)僧须。 經(jīng)常有香客問(wèn)我,道長(zhǎng)项炼,這世上最難降的妖魔是什么担平? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮锭部,結(jié)果婚禮上暂论,老公的妹妹穿的比我還像新娘。我一直安慰自己拌禾,他們只是感情好取胎,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著湃窍,像睡著了一般闻蛀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上您市,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天觉痛,我揣著相機(jī)與錄音,去河邊找鬼墨坚。 笑死秧饮,一個(gè)胖子當(dāng)著我的面吹牛映挂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盗尸,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼柑船,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了泼各?” 一聲冷哼從身側(cè)響起鞍时,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扣蜻,沒(méi)想到半個(gè)月后逆巍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莽使,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年锐极,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芳肌。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡灵再,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亿笤,到底是詐尸還是另有隱情翎迁,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布净薛,位于F島的核電站汪榔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肃拜。R本人自食惡果不足惜痴腌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爆班。 院中可真熱鬧衷掷,春花似錦、人聲如沸柿菩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)枢舶。三九已至懦胞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凉泄,已是汗流浹背躏尉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留后众,地道東北人胀糜。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓颅拦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親教藻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子距帅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容