成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

CurrentHashMap源碼剖析

shenhualong / 527人閱讀

摘要:因?yàn)樵诙嗑€程情況下無法判斷返回一個(gè)值到底是為還是為是非多線程的,所以可以為何為

什么是concurrenthashmap

concurrenthashmap(簡(jiǎn)稱chm) 是java1.5新引入的java.util.concurrent包的成員,作為hashtable的替代。為什么呢,hashtable采用了同步整個(gè)方法的結(jié)構(gòu)。雖然實(shí)現(xiàn)了線程安全但是性能也就大大降低了 而hashmap呢,在并發(fā)情況下會(huì)很容易出錯(cuò)。所以也促進(jìn)了安全并且能在多線程中使用的concurrenthashmap

如何實(shí)現(xiàn)concurrenthashmap

首先來看看構(gòu)造方法吧
這是最底層的構(gòu)造方法

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;//代表ssize轉(zhuǎn)換的次數(shù)
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift;//目前不知道有什么用,是在后來的segment定位中使用
        this.segmentMask = ssize - 1;//segment定位使用
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        Segment s0 =
            new Segment(loadFactor, (int)(cap * loadFactor),
                             (HashEntry[])new HashEntry[cap]);
        Segment[] ss = (Segment[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

這里我想和hashmap對(duì)比來分析,因?yàn)樗麄冮L得很像,hashmap是entry[],而chm就是segments[].可以說每一個(gè)segment都是一個(gè)hashmap,想要進(jìn)入segment還需要獲取對(duì)應(yīng)的鎖。默認(rèn)conccurrenthashmap的segment數(shù)是16.每個(gè)segment內(nèi)的hashentry數(shù)組大小也是16個(gè)。threadshord是16*0.75

chm如何定位
先看看chm的hash方法        
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);
    }

這里對(duì)key的hash值再哈希了一次。使用的方法是wang/jenkins的哈希算法,這里再hash是為了減少hash沖突。如果不這樣做的話,會(huì)出現(xiàn)大多數(shù)值都在一個(gè)segment上,這樣就失去了分段鎖的意義。
以上代碼只是算出了key的新的hash值,但是怎么用這個(gè)hash值定位呢

如果我們要取得一個(gè)值,首先我們肯定需要先知道哪個(gè)segment,然后再知道hashentry的index,最后一次循環(huán)遍歷該index下的元素

   確定segment,:(h >>> segmentShift) & segmentMask。默認(rèn)使用h的前4位,segmentMask為15
   確定index:(tab.length - 1) & h  hashentry的長度減1,用之前確定了sement的新h計(jì)算
   循環(huán):for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile
                           (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                       e != null; e = e.next)
                       
     比較:if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                            return e.value;

chm取得元素
public V get(Object key) {
        Segment s; // manually integrate access methods to reduce overhead
        HashEntry[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry e = (HashEntry) 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;
    }

如果我們要取得一個(gè)值,首先我們肯定需要先知道哪個(gè)segment,然后再知道hashentry的index,最后一次循環(huán)遍歷該index下的元素

       確定segment,:(h >>> segmentShift) & segmentMask。默認(rèn)使用h的前4位,segmentMask為15
       確定index:(tab.length - 1) & h  hashentry的長度減1,用之前確定了sement的新h計(jì)算
       循環(huán):for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);e != null; e = e.next)
       比較:if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                 return e.value;
chm 存放元素
  public V put(K key, V value) {
            Segment s;
            if (value == null)
                throw new NullPointerException();
            int hash = hash(key);
            int j = (hash >>> segmentShift) & segmentMask;
            if ((s = (Segment)UNSAFE.getObject          // nonvolatile; recheck
                 (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
                s = ensureSegment(j);
            return s.put(key, hash, value, false);
        }   
    在jdk中,native方法的實(shí)現(xiàn)是沒辦法看的,請(qǐng)下載openjdk來看。在put方法中實(shí)際是需要判斷是否需要擴(kuò)容的
    擴(kuò)容的時(shí)機(jī)選在閥值(threadshold)裝滿時(shí),而不像hashmap是在裝入后,再判斷是否裝滿并擴(kuò)容
    這里就是concurrenthashmap的高明之處,有可能會(huì)出現(xiàn)擴(kuò)容后就沒有新數(shù)據(jù)的情況
concrrenthashmap 容量判斷
public int size() {
        final Segment[] 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 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;
    }

這段代碼寫的真巧妙,在統(tǒng)計(jì)concurrenthashmap的數(shù)量時(shí),有多線程情況,但是并不是一開始就鎖住修改結(jié)構(gòu)的方法,比如put,remove等
先執(zhí)行一次統(tǒng)計(jì),然后在執(zhí)行一次統(tǒng)計(jì),如果兩次統(tǒng)計(jì)結(jié)果都一樣,則沒問題。反之就鎖修改結(jié)構(gòu)的方法。這樣做效率會(huì)高很多,在統(tǒng)計(jì)的時(shí)候查詢依舊可以進(jìn)行
chm是否為空判斷
public boolean isEmpty() {
       
        long sum = 0L;
        final Segment[] segments = this.segments;
        for (int j = 0; j < segments.length; ++j) {
            Segment seg = segmentAt(segments, j);
            if (seg != null) {
                if (seg.count != 0)
                    return false;
                sum += seg.modCount;
            }
        }
        if (sum != 0L) { // recheck unless no modifications
            for (int j = 0; j < segments.length; ++j) {
                Segment seg = segmentAt(segments, j);
                if (seg != null) {
                    if (seg.count != 0)
                        return false;
                    sum -= seg.modCount;
                }
            }
            if (sum != 0L)
                return false;
        }
        return true;
    }
即使在空的情況下也不能僅僅只靠segment的計(jì)數(shù)器來判斷,還是因?yàn)槎嗑€程,count的值隨時(shí)在變,所以追加判斷
modcount前后是否一致,如果一致,說明期間沒有修改。
chm刪除元素
final V remove(Object key, int hash, Object value) {
            if (!tryLock())
                scanAndLock(key, hash);
            V oldValue = null;
            try {
                HashEntry[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry e = entryAt(tab, index);
                HashEntry pred = null;
                while (e != null) {
                    K k;
                    HashEntry 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;
        }   
        
思考

1.hashmap的默認(rèn)大小是1<<4,即16,但是concurrenthashmap卻直接16.
2.(k << SSHIFT) + SBASE 這段話我是真沒懂,定位的時(shí)候會(huì)用
3.get方法中直接寫的定位方法,為什么不像remove一樣調(diào)用segmentforhash呢
4.concurrenthashmap和hashtable不能允許key或者value為null。因?yàn)樵诙嗑€程情況下無法判斷返回一個(gè)null值到底是key為null還是value為null
hashmap是非多線程的,所以可以key為null何value為null

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69569.html

相關(guān)文章

  • Swoft 源碼剖析 - 目錄

    摘要:作者鏈接來源簡(jiǎn)書著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。同時(shí)順手整理個(gè)人對(duì)源碼的相關(guān)理解,希望能夠稍微填補(bǔ)學(xué)習(xí)領(lǐng)域的空白。系列文章只會(huì)節(jié)選關(guān)鍵代碼輔以思路講解,請(qǐng)自行配合源碼閱讀。 作者:bromine鏈接:https://www.jianshu.com/p/2f6...來源:簡(jiǎn)書著作權(quán)歸作者所有,本文已獲得作者授權(quán)轉(zhuǎn)載,并對(duì)原文進(jìn)行了重新的排版。Swoft...

    qpwoeiru96 評(píng)論0 收藏0
  • Python貓薦書系統(tǒng)之四:《Python源碼剖析

    摘要:以下內(nèi)容僅針對(duì)版書籍,等新版上市后,薦書欄目會(huì)對(duì)兩版的差異跟進(jìn)介紹。當(dāng)然,后續(xù)其它薦書的書目,也很有可能會(huì)送福利,一樣不容錯(cuò)過。 showImg(https://segmentfault.com/img/bVbjIxq?w=6000&h=4000); 大家好,新一期的薦書欄目如期跟大家見面了。 先來看看今天的主角是誰:《Python源碼剖析——深度探索動(dòng)態(tài)語言核心技術(shù)》,2008年出版...

    simpleapples 評(píng)論0 收藏0
  • TreeMap就這么簡(jiǎn)單【源碼剖析

    摘要:在這種情況下,是以其為根的樹的最后一個(gè)結(jié)點(diǎn)。來源二總結(jié)底層是紅黑樹,能夠?qū)崿F(xiàn)該集合有序如果在構(gòu)造方法中傳遞了對(duì)象,那么就會(huì)以對(duì)象的方法進(jìn)行比較。 前言 聲明,本文用得是jdk1.8 前面章節(jié)回顧: Collection總覽 List集合就這么簡(jiǎn)單【源碼剖析】 Map集合、散列表、紅黑樹介紹 HashMap就是這么簡(jiǎn)單【源碼剖析】 LinkedHashMap就這么簡(jiǎn)單【源碼剖析】 本...

    ormsf 評(píng)論0 收藏0
  • Golang 源碼剖析:log 標(biāo)準(zhǔn)庫

    摘要:源碼剖析標(biāo)準(zhǔn)庫原文地址源碼剖析標(biāo)準(zhǔn)庫日志輸出構(gòu)成日期空格時(shí)分秒空格內(nèi)容源碼剖析互斥鎖,用于確保原子的寫入每行需寫入的日志前綴內(nèi)容設(shè)置日志輔助信息時(shí)間文件名行號(hào)的寫入。 Golang 源碼剖析:log 標(biāo)準(zhǔn)庫 原文地址:Golang 源碼剖析:log 標(biāo)準(zhǔn)庫 日志 輸出 2018/09/28 20:03:08 EDDYCJY Blog... 構(gòu)成 [日期][時(shí)分秒][內(nèi)容] 源碼剖析 L...

    stackvoid 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<