国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

java并發編程學習之ConcurrentHashMap(JDK1.7)

piglei / 3168人閱讀

摘要:之前中提過,并發的時候,可能造成死循環,那么在多線程中可以用來避免這一情況。默認,當容量大于時,開始擴容并發數,默認,直接影響和的值,以及的初始化數量。初始化的數量,為最接近且大于的辦等于的次方的值,比如,數量為,,數量為。

之前HashMap中提過,并發的時候,可能造成死循環,那么在多線程中可以用ConcurrentHashMap來避免這一情況。

Segment

ConcurrentHashMap是由多個Segment組成的,Segment繼承了ReentrantLock,每次加鎖都是對某個Segment,不會影響其他Segment,達到了鎖分離(也叫分段鎖)的作用。
每個Segment又包含了HashEntry數組,HashEntry是一個鏈表。如下圖所示:

初始化

initialCapacity:初始容量大小,默認16。
loadFactor:擴容因子,table擴容使用,Segments不擴容。默認0.75,當Segment容量大于initialCapacity*loadFactor時,開始擴容
concurrencyLevel:并發數,默認16,直接影響segmentShift和segmentMask的值,以及Segment的初始化數量。Segment初始化的數量,為最接近且大于的辦等于2的N次方的值,比如concurrencyLevel=16,Segment數量為16,concurrencyLevel=17,Segment數量為32。segmentShift的值是這樣的,比如Segment是32,相對于2的5次方,那么他的值就是32-5,為27,后面無符號右移27位,也就是取高5位的時候,就是0到31的值,此時Segment的下標也是0到31,取模后對應著每個Segment。segmentMask就是2的n次方-1,這邊n是5,用于取模。之前在hashmap的indexFor方法有提過。
初始化的時候,還要初始化第一個Segment,以及Segment中table數組的大小,這邊大小是大于等于initialCapacity除以Segment數組的個數,平均分配,最小是2,且是2的N次方。比如initialCapacity是32,concurrencyLevel是16的時候,那么Segment的個數也是16,32除以16,等于2,如果initialCapacity是33,Segment是16,33除以16,取4。

public ConcurrentHashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
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 <<= 1;
    }
    this.segmentShift = 32 - sshift;//用于高位,判斷落在哪個Segment
    this.segmentMask = ssize - 1;//用于取模。之前在hashmap的indexFor方法有提過。2的n次方-1
    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
    Segment[] ss = (Segment[])new Segment[ssize];//初始化Segments
    UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
    this.segments = ss;
}
put方法
public V put(K key, V value) {
    Segment s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    //無符號右移后取模,落在哪個Segment上面
    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);
}

ensureSegment方法
確定落在哪個Segment上,如果為空,就初始化,因為之前就初始化第一個Segment

private Segment ensureSegment(int k) {
    final Segment[] ss = this.segments;
    long u = (k << SSHIFT) + SBASE; // raw offset
    Segment seg;
    if ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) == null) {
        //使用segment[0]的table長度和loadFactor來初始化
        Segment proto = ss[0]; // use segment 0 as prototype
        int cap = proto.table.length;
        float lf = proto.loadFactor;
        int threshold = (int)(cap * lf);
        HashEntry[] tab = (HashEntry[])new HashEntry[cap];
        if ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u))
            == null) { // recheck
            Segment s = new Segment(lf, threshold, tab);
            while ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u))//cas操作,只能一個設值成功,如果其他成功了,就賦值,并返回
                   == null) {
                if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                    break;
            }
        }
    }
    return seg;
}

put方法

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    HashEntry node = tryLock() ? null :
        scanAndLockForPut(key, hash, value);//獲取Segment的鎖
    V oldValue;
    try {
        HashEntry[] tab = table;
        int index = (tab.length - 1) & hash;//上面是獲取Segment取高位的hash,這邊是tabel的hash,
        HashEntry first = entryAt(tab, index);//取到hash位置的數組的表頭
        for (HashEntry e = first;;) {//從頭結點遍歷
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {//key相同,或者hash值一樣
                    oldValue = e.value;
                    if (!onlyIfAbsent) {//是否替換
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                if (node != null)//不為空,設置為表頭
                    node.setNext(first);
                else
                    node = new HashEntry(hash, key, value, first);/初始化后放表頭
                int c = count + 1;
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);// 擴容
                else
                    setEntryAt(tab, index, node);//把新的節點放在tab的index上面
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();//釋放鎖
    }
    return oldValue;
}

scanAndLockForPut方法
嘗試獲取鎖,沒獲取到先初始化node

private HashEntry scanAndLockForPut(K key, int hash, V value) {
    HashEntry first = entryForHash(this, hash);//獲取hash后的頭結點,有存在null的情況
    HashEntry e = first;
    HashEntry node = null;
    int retries = -1; // negative while locating node
    while (!tryLock()) {//這個put方法先嘗試獲取,獲取不到,這邊while循環嘗試獲取
        HashEntry f; // to recheck first below
        if (retries < 0) {
            if (e == null) {//結點為空的時候
                if (node == null) // speculatively create node
                    node = new HashEntry(hash, key, value, null);//初始化node
                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) {//不等于first,就是已經有其他節點進入
            e = first = f; // re-traverse if entry changed
            retries = -1;
        }
    }
    return node;
}

rehash方法,擴容,對table擴容

private void rehash(HashEntry node) {
    HashEntry[] oldTable = table;
    int oldCapacity = oldTable.length;
    int newCapacity = oldCapacity << 1;//左移,之前的2倍
    threshold = (int)(newCapacity * loadFactor);
    HashEntry[] newTable =
        (HashEntry[]) new HashEntry[newCapacity];
    int sizeMask = newCapacity - 1;
    for (int i = 0; i < oldCapacity ; i++) {
        HashEntry e = oldTable[i];
        if (e != null) {
            HashEntry next = e.next;
            int idx = e.hash & sizeMask;
            if (next == null)   //  為空,沒有后面的節點,直接給新數組
                newTable[idx] = e;
            else { // Reuse consecutive sequence at same slot
                HashEntry lastRun = e;
                int lastIdx = idx;
                //因為數組是2倍的擴容,所以重新hash后,要么落在跟之前索引一樣的位置,要么就是加上oldCapacity 的值,
                //比如容量是2,擴容4,現在hash是2,4,6,10,14那么后面3個都是除4余2,可以直接復制
                for (HashEntry last = next;
                     last != null;
                     last = last.next) {
                    int k = last.hash & sizeMask;
                    if (k != lastIdx) {//hash不一樣,重新
                        lastIdx = k;
                        lastRun = last;
                    }
                }
                //執行上面,就是lastRun是6,10,14
                newTable[lastIdx] = lastRun;//上面
                // Clone remaining nodes克隆的時候,碰到lastrun,直接根據所以給值,但是前面有可能的索引跟lastrun一樣,比如2
                for (HashEntry p = e; p != lastRun; p = p.next) {
                    V v = p.value;
                    int h = p.hash;
                    int k = h & sizeMask;
                    HashEntry n = newTable[k];
                    newTable[k] = new HashEntry(h, p.key, v, n);
                }
            }
        }
    }
    int nodeIndex = node.hash & sizeMask; // add the new node
    node.setNext(newTable[nodeIndex]);//加入到頭結點
    newTable[nodeIndex] = node;
    table = newTable;
}
get方法
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 &&//找到Segment,邏輯同put
        (tab = s.table) != null) {
        for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);//找到table,邏輯同put
             e != null; e = e.next) {//遍歷table
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75648.html

相關文章

  • Java 容器習之 HashMap

    摘要:底層的數據結構就是數組鏈表紅黑樹,紅黑樹是在中加進來的。負載因子哈希表中的填滿程度。 前言 把 Java 容器的學習筆記放到 github 里了,還在更新~其他的目前不打算抽出來作為文章寫,感覺挖的還不夠深,等對某些東西理解的更深了再寫文章吧Java 容器目錄如下: Java 容器 一、概述 二、源碼學習 1. Map 1.1 HashMap 1.2 LinkedHashM...

    Alex 評論0 收藏0
  • 這幾道Java集合框架面試題在面試中幾乎必問

    摘要:若遇到哈希沖突,則將沖突的值加到鏈表中即可。之后相比于之前的版本,之后在解決哈希沖突時有了較大的變化,當鏈表長度大于閾值默認為時,將鏈表轉化為紅黑樹,以減少搜索時間。有序,唯一紅黑樹自平衡的排序二叉樹。 本文是最最最常見Java面試題總結系列第三周的文章。主要內容: Arraylist 與 LinkedList 異同 ArrayList 與 Vector 區別 HashMap的底層...

    bigdevil_s 評論0 收藏0
  • java并發編程習之synchronize(一)

    摘要:線程安全問題在并發編程學習之基礎概念提到,多線程的劣勢之一,有個線程安全問題,現在看看下面的例子。那么,該怎么解決呢,很簡單,在方法前加個同步鎖。運行結果如下有兩種情況,是因為看誰先搶占鎖,但是輸出的算法結果是正確的。 線程安全問題 在java并發編程學習之基礎概念提到,多線程的劣勢之一,有個線程安全問題,現在看看下面的例子。 public class NotSafeDemo { ...

    Elle 評論0 收藏0
  • java并發編程習之再談公平鎖和非公平鎖

    摘要:在并發編程學習之顯示鎖里有提過公平鎖和非公平鎖,我們知道他的使用方式,以及非公平鎖的性能較高,在源碼分析的基礎上,我們看看和的區別在什么地方。而非公平鎖直接嘗試獲取鎖。 在java并發編程學習之顯示鎖Lock里有提過公平鎖和非公平鎖,我們知道他的使用方式,以及非公平鎖的性能較高,在AQS源碼分析的基礎上,我們看看NonfairSync和FairSync的區別在什么地方。 lock方法 ...

    warkiz 評論0 收藏0
  • java并發編程習之FutureTask

    摘要:在并發編程學習之三種線程啟動方式中有提過。是否執行結束,包括正常執行結束或異常結束。獲取返回值,沒有得到返回值前一直阻塞。運行結果如下由于任務被取消,所以拋出異常。注意的是,此時線程還在跑,和返回的是。并不能讓任務真正的結束。 FutureTask 在java并發編程學習之三種線程啟動方式中有提過。主要的方法如下: cancel(boolean mayInterruptIfRunni...

    BothEyes1993 評論0 收藏0

發表評論

0條評論

piglei

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<