久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁技術文章
文章詳情頁

JAVA核心知識之ConcurrentHashMap源碼分析

瀏覽:115日期:2022-08-14 16:59:49
1 前言

ConcurrentHashMap是基于Hash表的Map接口實現,鍵與值均不允許為NULL,他是一個線程安全的Map。同時他也是一個無序的Map,不同時間進行遍歷可能會得到不同的順序。在JDK1.8之前,ConcurrentHashMap使用分段鎖以在保證線程安全的同時獲得更大的效率。JDK1.8開始舍棄了分段鎖,使用自旋+CAS+sync關鍵字來實現同步。本文所述便是基于JDK1.8。ConcurrentHashMap與HashMap有共同之處,一些HashMap的基本概念與實現,本文不再贅述。

2 繼承關系

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, Serializable

可以看到ConcurrentHashMap繼承了AbstractMap及ConcurrentMap抽象類,并實現了Serializable接口,這說明ConcurrentHashMap是一個線程安全的標準Map,且允許序列化。與HashMap不同是ConcurrentHashMap不允許Clone。

3 構造方法

ConcurrentHashMap同樣是采用懶初始化的方式,有實際元素時才進行容器的初始化。因此其構造方法與HashMap相差無幾。

// 無參構造public ConcurrentHashMap() {}// 帶有初始容量的構造public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0)throw new IllegalArgumentException(); int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); //求最小二次冪 this.sizeCtl = cap; // sizeCtl 暫存容量}// 帶有初始集合的構造public ConcurrentHashMap(Map<? extends K, ? extends V> m) { this.sizeCtl = DEFAULT_CAPACITY; putAll(m);}// 指定初始容量和裝載因子的構造public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, 1);}

以上構造并沒有什么特別的,邏輯也相對簡單,不再詳細解析,感興趣的話可以到前言提到的HashMap篇了解。除此之外,ConcurrentHashMap擁有另外一個值得注意的構造方法:指定初始容量,裝載因子以及并發級別的構造方法:源碼:

public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) // 基礎校驗throw new IllegalArgumentException(); if (initialCapacity < concurrencyLevel) // 取初始容量和并發級別的最大值initialCapacity = concurrencyLevel; long size = (long)(1.0 + (long)initialCapacity / loadFactor); // 指定了裝載因子,因此就用初始容量和裝載因子計算出實際要的初始容量 int cap = (size >= (long)MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);// 確定計算出初始容量, this.sizeCtl = cap;}

解析: 除了初始容量與裝載因子外,此構造方法還有一個并發級別concurrencyLevel的參數。在jdk1.7時,并發級別作為分段鎖的標準進行分段。但是jdk1.8開始舍棄了分段鎖,為了版本兼容,此構造方法依然存在,但是concurrencyLevel也不再具有其分段依據的意義,而是作為初始容量的定義依據。

4 初始化

ConcurrentHashMap采用懶初始化的方式,在第一次putAvl時如果容器為空,則會調用initTable()進行容器的初始化:

private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { // tab為空時就一直循環if ((sc = sizeCtl) < 0) // sc小于0說明正在初始化或者正在復制,放棄CPU等下一次 Thread.yield(); // lost initialization race; just spinelse if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // 否則的話就用CAS把sizectl設置成-1,標識正在初始化,且成功的話 try {// CAS之后再判斷一下,這是為了避免并發,比如上面判斷了tab為空,然后另一個線程做了初始化操作,結束時sc被設置為擴容閾值,然后繼續(sc = sizeCtl) < 0,這時cs還是大于0,所以還是能走進來的,所以這里再判斷一下if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; // sc最初始是存在的初始容量的 @SuppressWarnings('unchecked') Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; // 建立一個容器 table = tab = nt; // 然后復制到table屬性上 sc = n - (n >>> 2); // sc等于 n>>>2就是n/4就是0.25n sc = 0.75n,相當于設置擴容閾值} } finally {sizeCtl = sc; // sizeCtl賦值成擴容閾值 } break; // 只要走到這里面,那么說明一定已經初始化結束了,無論初始化是自己做的還是其他線程做的,然后就跳出} } return tab; // 返回新的tab}

解析: 容器初始化完成之前不斷的進行循環。這是因為ConcurrentHashMap是一個支持并發的Map,可能同時會有多個線程進入initTable方法,但是只有一個線程執行初始化操作,那么剩下的線程就需要等待初始化完成再跳出initTable方法,以滿足接下來的putVal操作。為了保證只有一個線程執行初始化操作,使用sizeCtl來作為標識,sizeCtl為-1時即說明當前已有線程正在初始化,則放棄CPU繼續循環。sizeCtl不為負數時,則使用CAS(通過Unsafe+偏移量的方式實現)將sizeCtl置為-1,如果當前線程成功的話則進入實際的擴容操作。通過CAS鎖定成功后再次判斷容器是否為空,這是為了避免并發,比如上面判斷了tab為空,然后另一個線程做了初始化操作,結束時sc被設置為擴容閾值,然后繼續(sc = sizeCtl) < 0,這時cs還是大于0,所以還是能走進來的,所以這里再判斷一下。如果sc(sizeCtl原值)大于0,則以sc做為初始容量。這個在構造方法篇提到過,對于有初始容量要求的構造,會以sizeCtl暫存初始容量。否則的話就取默認的初始容量DEFAULT_CAPACITY(16)。接下來就建立目標容量的容器并賦值給容器屬性table,計算該容量下的擴容閾值賦值給sizeCtl 。sizeCtl的賦值放到finally里面的原因是因為無論容器創建成功還是失敗,都需要放開以sizeCtl為負值作為判斷條件的鎖,以保證在本線程創建失敗的情況其它線程能繼續競爭鎖繼續進行容器創建的工作。至此,容器的初始化便完成了。

5 新數據載入-putVal

ConcurrentHashMap新數據載入主要通過putVal實現:

final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { // 會一直循環,直到breakNode<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0) tab = initTable(); // 初始化tableelse if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // unsafe獲取i處的數據,如果為null if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) // cas操作,如果i處為null就構造新元素,成功就break 失敗就繼續循環break; // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED)// f處的元素hash是-1 -1意味著這是一個遷移節點 tab = helpTransfer(tab, f); // 輔助遷移開始else { V oldVal = null; synchronized (f) { // 用f進行同步if (tabAt(tab, i) == f) { // 這里要保證i處還是f,而不是已經被其它元素并發占據了 if (fh >= 0) { // f處的hash要大于等于0,這是為什么呢?除了MOVED還會有其它負值嗎binCount = 1;for (Node<K,V> e = f;; ++binCount) { // 從e往后找,并且記錄元素數量 K ek; if (e.hash == hash &&((ek = e.key) == key || (ek != null && key.equals(ek)))) { // 一系列判斷Key是否相等,如果相等oldVal = e.val; // 記錄舊值if (!onlyIfAbsent) // 不是僅允許新增的話 e.val = value; // 記錄新值break; // 成功的話就跳出大循環 } Node<K,V> pred = e; if ((e = e.next) == null) { // e向后移動, 如果移動之后是nul,就是在末尾的話pred.next = new Node<K,V>(hash, key, value, null); // 建立一個新節點break; // 成功的話就跳出大循環 }} } else if (f instanceof TreeBin) { // 如果是一個樹化節點Node<K,V> p;binCount = 2; // 樹化的節點的話固定元素數量為2,這是一個相對特殊的值,即會擴容又不會重復樹化if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { // 往樹里面新增元素,如果存在就返回那個節點,不存在就是新增節點 oldVal = p.val; // 記錄舊值 if (!onlyIfAbsent) // 不是僅允許新增的話p.val = value; // 記錄新值} }} } if (binCount != 0) { // 如果元素數不為0的話if (binCount >= TREEIFY_THRESHOLD) // 判斷是否到了樹化閾值 treeifyBin(tab, i); // 到了的話進行樹化if (oldVal != null) // 覆蓋操作直接返回舊值不會執行addCount return oldVal;break; // binCount != 0才跳出大循環,為0是tabAt(tab, i) != f導致的,即i處已經被其他線程覆蓋了 }} } addCount(1L, binCount); // return null;}

解析: 完成為空校驗后,通過spread方法來計算出hash以確定下標位置,spread的計算方式為(h ^ (h >>> 16)) & HASH_BITS,HASH_BITS的值為0x7fffffff,描述出來就是將hashCode的高16位和低16位做異或操作,并保證最高位符號位為0(結果是一個正數),注解表示這樣做的原因是為了使數據更加分散,盡可能的避免hash沖突。如果容器tab為空或者長度為0說明容器未初始化,那么就調用initTable進行容器初始化。否則的話對hash與現容量進行與運算得出數據應處的下標,判斷此下標所在處節點是否為空,為空說明沒有元素,直接創建一個新節點作為頭元素放進去,這一步通過CAS操作實現,避免并發情況下另外的線程先一步完成頭節點創建操作。如果下標所在處節點不為空,說明該處已經有元素了,此時判斷頭節點的hash是否為MOVED,MOVED的值為-1。上面提到過,正常元素通過spread方法計算出來的hash值都會使正數。此處-1為一個特殊值,意味著此節點正在進行擴容遷移工作,那么此時就調用8 輔助擴容-helpTransfer方法進行輔助遷移工作。如果節點hash不為MOVED,意味著這是一個正常節點,就執行元素載入工作,使用synchronized關鍵字實現同步, 同步塊內開始還要使用tabAt(tab, i) == f判斷進入同步后i處的節點還是原節點,接下來就是元素的載入工作了,整體和HashMap的流程是相差無幾的,感興趣的話可以去文首提到的解析HashMap的文章了解一下。synchronized塊將元素載入節點后,接著會對binCount進行判斷,binCount在節點還處于鏈表模式下的情況下記錄節點在新元素載入后的元素總數量,此處判斷binCount達到TREEIFY_THRESHOLD(8) 樹化閾值后,就會對該節點進行樹化操作。樹化操作依然是通過synchronized來完成的,這里不做過多延伸。以上整個新元素載入操作都是一個自旋的過程,一直到新元素載入成功后通過break跳出自旋過程。新元素載入節點后,需要對count實時維護,count通過 6 維護與啟動擴容-addCount 方法完成同步維護工作。

6 維護與啟動擴容-addCount

ConcurrentHashMap通過addCount方法實時維護內部元素數量,并在達到擴容閾值的情況下啟動擴容操作:

private final void addCount(long x, int check) { CounterCell[] as; long b, s; if ((as = counterCells) != null || // counterCells不為空,如果沒經歷過并發肯定是空的,這個判斷意思是維護count的過程中已經經歷過并發,已經處于不使用baseCount維護的階段了!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { //這個比較唯一會出現false的情況就是b = baseCount執行之后,還沒進行cas時,baseCount又被改了。意思是如果沒有并發就用baseCount維護,并發導致baseCount維護失敗就進入counterCells維護CounterCell a; long v; int m;boolean uncontended = true; // 記錄是否遭遇了并發if (as == null || (m = as.length - 1) < 0 || // as等于null意味著baseCount維護遭遇并發了,且是初次(非初次counterCells不會為null) (m = as.length - 1) < 0說明counterCells長度為0,即沒有實際內容 (a = as[ThreadLocalRandom.getProbe() & m]) == null || // counterCells是一個分段計算的概念,這里就是隨機找一個槽,如果這個槽位空的話 !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { // 槽不為空的話就用cas把槽里面的值賦值給v+a.val 如果失敗的話 fullAddCount(x, uncontended); // 用fullAddCount 就是LongAdder的模式將x放進去,初始化counterCells return;}if (check <= 1) // check小于等于1(1表示是當前節點的第一個元素,-1標識是replace方法或者clear過來的,這些都不需要擴容) return;s = sumCount(); // 計算一下現在的總量 } if (check >= 0) { //baseCount成功的情況且 check大于0,即不是replace或者clear方法進來的Node<K,V>[] tab, nt; int n, sc;/**1.s >= (long)(sc = sizeCtl) s是總數量 s大于等于擴容閾值sizeCtl時,或者sizeCtl為負數,正在擴容時2.(tab = table) != null 并且容器tab不為空,意思就是表已經初始化完畢了,是執行擴容而不是執行初始化3. (n = tab.length) < MAXIMUM_CAPACITY) 并且容量還沒有達到最大容量時 */while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { int rs = resizeStamp(n); // 獲得容量特征值:容量n的左0數量并將高位置1 if (sc < 0) { // 如果sc小于0 意味這個正在進行并發擴容/**sc >>> RESIZE_STAMP_SHIFT != rs是判斷現在的容量與sc中記錄的長度是不是不一樣長,不一樣長的話說明不是同一長度的擴容,就要break-保證是同等長度的擴容sc == rs + 1 這個地方在看來是一個bug,如果已經處于擴容中,sc目前處于高16位是容量特征值,低16位標識擴容線程量特征值的狀態,而此時的rs是容量特征值的狀態,也就是目前sc的高16位,這里原意應該是判斷目前是不是只剩下一個非初始擴容線程 ,為什么這么說呢,因為初始擴容線程會把sc設置稱高16位是容量特征值即rs,低16位標識擴容線程量特征值(初始擴容線程+2,非初始擴容線程+1),這一步判斷的就是如過只剩下一個非初始擴容線程了,說明初始擴容線程已經結束,現在已經進入擴容的最后階段了,就不需要再繼續進行輔助擴容了 ,正確的方式應該是sc == (rs << RESIZE_STAMP_SHIFT) + 1sc == rs + MAX_RESIZERS 和上面+1一樣,不過這個是判斷參與resize的線程是否已經達到所允許的最大值 ,看起來也是一個bug,正確的應是sc == (rs << RESIZE_STAMP_SHIFT) + MAX_RESIZERS(nt = nextTable) == null nextTable為空說明已經擴容完畢了,不再需要輔助擴容了transferIndex <= 0 // 判斷下邊界是不是已經到0了,擴容是根據CPU算出步長,一個擴容線程擴容一段,從高到低分配,transferIndex記錄的就行目前分配到哪個下標了,如果這個下標小于等于0說明整個容器已經分配完了,就不需要在輔助擴容了 */if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) break;if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) // 如果是輔助擴容的sc已經處于高16位是容量特征值,低16位標識擴容線程量特征值的狀態了+1讓地位的擴容線程量特征值再+1 transfer(tab, nt); // 進行擴容 } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) // 初始擴容現在是+2 執行完這一步sc的值高16位是容量特征值,低16位標識擴容線程量特征值,因為resizeStamp時將高位置為1是的其又是一個負數,表示正在擴容transfer(tab, null); s = sumCount(); // 計算總數} // 一直循環擴容 }}

解析: count的維護有兩種方式:未產生并發場景時通過baseCount維護,經歷過并發場景后轉變為通過counterCells維護。baseCount模式為直接使用int進行加減運算,cas保證同步,counterCells模式類似于1.8之前的ConcurrentHashMap,采用一個分段的概念,運算時隨機找一個槽,通過cas保證一個槽的值加算同步,count即為所以槽的加和(使用LongAdder實現)。addCount方法初始即通過counterCells是否為空(為空說明未經歷初始化,使用的baseCount模式)來判斷當前是否為counterCells模式,如果處于counterCells模式則進入counterCells模式計算邏輯,否則的話使用baseCount模式來維護count并記錄為s,如果遭遇并發導致維護失敗,則轉換為counterCells模式進入counterCells模式計算邏輯。進入counterCells模式,首先看counterCells是否已完成初始化,若為初始化進入fullAddCount,已初始化的話則隨機找到counterCells中的一個槽位,如果這個槽位未初始化則進入fullAddCount,如果已初始化則對這個操作進行CAS的加操作來維護count,CAS成功則維護完成,并發導致CAS維護失敗則進入fullAddCount。fullAddCount有點類似于CMS的full GC退化機制,會完成counterCells的初始化以及并發沖突場景下同步完成count維護。counterCells最后則判斷check是否小于等于1(1表示是當前節點的第一個元素,-1標識是replace方法或者clear過來的,這些都不需要擴容)則直接結束,不進行擴容判定,否則的話通過sumCount獲得當前count并記錄為s以準備后續的擴容判定。check大于等于0(即不是replace或者clear方法引起的count改變)時進行擴容判定,執行擴容需要滿足三個條件:1.count大于擴容閾值或者當前正在執行擴容操作2.容器已初始化完畢3.容量未達到最大容量。滿足以上條件則需判定當前是否正在擴容,因為擴容時會將SIZECTL設置為一個特征值,這個特征值為負值,因此通過sc是否小于0來判定當前是否處于擴容狀態。接下來使用resizeStamp獲得容量特征值(一個表示容量n的左0數量并將高位置1的值),如果當前未處于擴容過程,則通過容量特征值獲得擴容特征值(高16位為容量特征值,低16位為擴容線程量特征值)并通過CAS修改SIZECTL為擴容特征值,成功則當前線程作為第一個擴容線程啟動擴容,失敗則重新計算count并進行擴容判定。如果已處于擴容過程,則判斷是否還要寫輔助擴容線程,滿足以下幾個條件則:1.此次擴容的容量與當前正在進行的容量一致2.擴容線程未達到最大額定值3.不是處于擴容收尾階段(只剩下一個輔助擴容線程,nextTable已經為空,擴容下標已為0無法繼續分配)則使用CAS修改擴容特征值成功后當前線程作為輔助擴容線程加入擴容任務,不滿足條件則重新計算count并自旋進行擴容判定。

7 進行擴容-transfer

ConcurrentHashMap的實際擴容操作通過transfer來完成:

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { int n = tab.length, stride; // CPU大于1的話 步長就是0.25n/CPU數 否則就是n 算出來的步長小于最小步長的話 就按最小步長來 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE; // subdivide range if (nextTab == null) { // initiating // nextTab == null 說明是第一個來擴容的線程try { @SuppressWarnings('unchecked') Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; // 建立一個二倍長度的數組,然后賦值給nextTab nextTab = nt;} catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; // 異常的話就是OOM? 唯一的異常也就是n過大? return;}nextTable = nextTab; // 這個賦值為什么不做同步呢?不怕并發問題嗎,哦 不會 因為調用這個方法的地方已經確定了,只會有一個初始線程過來transferIndex = n; // n就是新的長度 } int nextn = nextTab.length; // 新容量 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); // 創建一個中繼節點 boolean advance = true; //一個終止的標記 boolean finishing = false; // to ensure sweep before committing nextTab // 是否已經處于收尾掃描提交階段 for (int i = 0, bound = 0;;) { // 一直循環著按步長擴容Node<K,V> f; int fh;while (advance) { // 計算出bound和i 賦值TRANSFERINDEX int nextIndex, nextBound; if (--i >= bound || finishing)// i大于bound,說明什么呢,說明i已經在區間內了advance = false; else if ((nextIndex = transferIndex) <= 0) { 如果下一次的上邊界已經到0了 也停止i = -1;advance = false; } else if (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? //nextBound作為新的上邊界 nextIndex - stride : 0))) {bound = nextBound;//nextBound作為這次的下邊界i = nextIndex - 1; // 從nextIndex(上邊界) - 1開始依次遞減到bound區域advance = false; }}if (i < 0 || i >= n || i + n >= nextn) { // 如果i不在0-n范圍內,已經越界 int sc; if (finishing) { // 如果已經在收尾階段nextTable = null; // 重置nextTabletable = nextTab; // 容器確認sizeCtl = (n << 1) - (n >>> 1); // 2n-0.5n = 0.75*2n 就是新的擴容閾值return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { // sc減1 意味當前擴容線程結束if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) // 如果sc-2和容量特征值一樣時,就說明現在是最后的一個擴容線程了 return; // 直接返回finishing = advance = true; // 不是最后的話 設置為收尾階段?,確實,進入這里面已經越界了...i = n; // recheck before commit // 提交前的重復檢查? }}else if ((f = tabAt(tab, i)) == null) // 如果i處還沒有使用 advance = casTabAt(tab, i, null, fwd); // cas設置i處的新fwdnode,成功還是失敗會記錄在advance上面else if ((fh = f.hash) == MOVED) // 如果這節點已經是一個中繼節點 說明什么呢?說明已經有人處理過了上一步 advance = true; // already processedelse { // 如果有值,并且不是中繼節點的話 synchronized (f) { // 同步處理f,所以只會有一個線程作為參與者if (tabAt(tab, i) == f) { // 同步之后還要再判斷一下 萬一f被并發處理掉了呢 Node<K,V> ln, hn; if (fh >= 0) { // fh>=0說明是一個還沒樹化的鏈表?int runBit = fh & n; Node<K,V> lastRun = f;for (Node<K,V> p = f.next; p != null; p = p.next) { int b = p.hash & n; if (b != runBit) { // 這是找最后一段的開頭節點runBit = b;lastRun = p; }}if (runBit == 0) { // 最后一段的開頭節點是低位的話 ln = lastRun; // 開頭節點賦值到ln 也就是說從lastRun開始,之后的都沒有變化了,都是在同一位 hn = null;}else { hn = lastRun; ln = null;}for (Node<K,V> p = f; p != lastRun; p = p.next) { // 只需遍歷到lastRun即可 int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0)ln = new Node<K,V>(ph, pk, pv, ln); // 往前補 elsehn = new Node<K,V>(ph, pk, pv, hn); // 往前補}setTabAt(nextTab, i, ln); // 賦值新容器setTabAt(nextTab, i + n, hn);setTabAt(tab, i, fwd); // 舊容器節點變為中繼節點,但是如果在這一步之前就有新元素來了怎么辦,不會來,因為新增也是sync(f)的advance = true; } else if (f instanceof TreeBin) { // 如果是一個樹化節點TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> lo = null, loTail = null;TreeNode<K,V> hi = null, hiTail = null;int lc = 0, hc = 0;for (Node<K,V> e = t.first; e != null; e = e.next) { int h = e.hash; TreeNode<K,V> p = new TreeNode<K,V>(h, e.key, e.val, null, null); if ((h & n) == 0) {if ((p.prev = loTail) == null) lo = p;else loTail.next = p;loTail = p;++lc; } else {if ((p.prev = hiTail) == null) hi = p;else hiTail.next = p;hiTail = p;++hc; }}ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : // 退化或者重新樹化 (hc != 0) ? new TreeBin<K,V>(lo) : t;hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t;setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);setTabAt(tab, i, fwd);advance = true; }} }} }}

解析: transfer采用分段擴容的方式,從n-1->0,每個線程每次會占用一個步長的區間,然后針對這個區間進行擴容,擴容完畢再去占用下一個區間,直到無法占用新的區間則結束擴容流程。步長的計算與應用的CPU數有關,如果當前應用CPU數為1則步長為n,即單線程庫擴容。如果當前應用CPU數不為1,則為0.25n/CPU,但是如果算出的步長小于最小MIN_TRANSFER_STRIDE(16),則以MIN_TRANSFER_STRIDE為步長。接下來通過nextTable 是否為空判斷是否為初始擴容線程,如果是的話就創建一個長度為2n的新容器并記錄到nextTable屬性,所有擴容線程共同使用nextTable作為新容器,同時記錄當前擴容進度下標transferIndex為n(擴容占用是從n到0的,因此初始未占用時就是n)。這里雖然沒進行加鎖但是依然不會有并發問題,因為nextTable為空的只有初始線程,而初始線程的創建則是線程安全的。步長確認完畢,新容器創建完畢,擴容進度下標初始化完成,接下來就開始進行區間占用了:使用自旋+CAS來進行線程安全的區間占用,advance作為自旋結束的條件:如果i大于bound意味著本次占用的區間還沒處理完則結束自旋。finishing意味著擴容已經進入尾聲了,也無需再加入擴容,結束自旋。擴容進度下標transferIndex小于等于0說明所有的區間都已經被占用了也無需再加入擴容,結束自旋。如果以上條件不成立,則使用CAS修改擴容進度下標transferIndex嘗試占用一個步長的區間,如果失敗則自旋繼續判定區間占用,修改成功則意味著區間占用成功,賦值本次區間的擴容游標i以及下標邊界bound。區間占用成功后開始從i到bound的擴容階段,首先需要判斷i的合法性(需要在i和n之間,即判斷是不是已擴容完畢,如?i的循環會使得i為負數表示擴容完畢),如果沒占據到區間則判斷是不是處于于finishing階段,處于的話說明自己以及是最后一個擴容線程了,將新容器nextTable賦值為table,并設置新的擴容閾值。如果不是處于finishing階段則因為當前線程沒占據到區間需要退出擴容,那么響應的擴容特征值也要相應的-1來完成擴容線程量特征值的維護,如果擴容特征值維護失敗則需要繼續自旋嘗試退出,如果維護成功則判斷自己退出之后是不是只能先一個擴容線程,因為起始擴容線程的線程擴容量特征值為+2,所以通過-2來判斷,如果自己退出之后僅剩一個線程在擴容,那么就把finishing設置為true,以使其在擴容工作結束后進行新容器的賦值以及新擴容閾值的維護。如果i處于合法范圍內,說明處于正常擴容中,那么獲取i處的節點,節點為空則利用一個CAS為其賦值一個ForwardingNode節點,這個節點是一個中繼節點,意味著這個節點正處于擴容狀態,其hash值為特殊值MOVED(-1),在5 新數據載入-putVal 的過程中如果發現目標節點hash值為MOVED,那么putVal線程就會暫停put操作,作為輔助擴容線程先行擴容容器,擴容完畢后再進行put操作。如果i處節點不會空但是hash值已經是MOVED,那么說明節點已經處于遷移狀態則跳過。如果以上均不符合,說明i合法,且i處擁有實際的數據節點,那么使用i處的節點f作為鎖通過synchronized來達成線程安全的擴容,因為putVal過程中也是用i處的節點f作為鎖進行synchronized的,意味著對i處的操作擴容和賦值只有一個過程能操作,以此來保證putVal和transfer的并發安全性。synchronized內部的擴容過程因為已經保證了線程同步,因此和HashMap的擴容過程區別并不大,這里不再描述,感興趣的話可以查看9: 從源碼看HashMap:一文看懂HashMap了解。

8 輔助擴容-helpTransfer

5 新數據載入-putVal 中以及7 進行擴容-transfer中都提到過如果putVal過程中發現目標節點是一個中繼節點ForwardingNode(hash值為特殊值MOVED)那么putVal會暫停put操作,通過helpTransfer方法進行輔助擴容:

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {Node<K,V>[] nextTab; int sc;if (tab != null && (f instanceof ForwardingNode) &&(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {int rs = resizeStamp(tab.length);while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) {if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || transferIndex <= 0)break;if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {transfer(tab, nextTab);break;}}return nextTab;}return table;}

解析: 這個方法是不是看起來很熟悉,是的,除了ForwardingNode判斷,其主要邏輯和6 維護與啟動擴容-addCount判定擴容時的邏輯大致相當。因此也不在額外表述。這里貼出來的目的僅僅是為了完整的體現putVal遭遇擴容并發的處理過程。

以上就是JAVA核心知識之從源碼看ConcurrentHashMap的詳細內容,更多關于JAVA核心知識之從源碼看ConcurrentHashMap的資料請關注好吧啦網其它相關文章!

標簽: Java
相關文章:
主站蜘蛛池模板: 国产超碰人人爽人人做人人爱 | 欧美黑人一级爽快片淫片高清 | 亚色在线| 看亚洲a级一级毛片 | 国产剧情一区二区 | 国产精品无码久久久久 | 久二影院| 最新中文字幕在线 | 久久久久久一区 | 91网址| 97在线观看视频 | 日韩精品免费 | 精品久久久久久久久久久久 | 精品九九 | 欧美一级特黄aaaaaaa色戒 | 国产丝袜一区 | 婷婷在线视频 | 欧美精品一区久久 | 黄色片免费观看网站 | 久久精品免费国产 | 久久成人国产精品 | 99国产精品久久久久久久 | 日韩乱码中文字幕 | 欧美日韩成人 | 玖玖精品在线 | 欧美午夜精品久久久久久人妖 | 欧美日韩国产一区二区三区不卡 | 激情毛片 | 久久精品2 | 精品96久久久久久中文字幕无 | 一区二区精品 | 国产乱码精品一区二区三区忘忧草 | 亚洲第一av | 国产精品美女久久久久久久网站 | 夜夜春精品视频高清69式 | 久久久久久久国产精品 | 亚洲 欧美 日韩 在线 | 亚洲精品国品乱码久久久久 | 久久久婷 | 国产精品久久久久久吹潮 | 国产最新视频在线 | 最新国产视频 | 欧美黑人一级爽快片淫片高清 | 91精品国产色综合久久不卡98口 | 国产成人一区二区三区 | 亚洲日本欧美日韩高观看 | 波多野结衣电影一区 | 欧美中文在线 | 久久国产精品一区二区 | 日韩超碰在线观看 | 久久男人天堂 | 夜夜艹日日艹 | 日本一区二区三区四区 | 久久成人精品一区二区三区 | 成人高清 | 91在线看| 91久久久久久久久久久久久 | 噜噜噜天天躁狠狠躁夜夜精品 | 国产精成人 | 免费观看一区二区三区毛片 | 可以在线看的黄色网址 | 久久99精品国产99久久6男男 | 国产欧美一区二区精品久久 | 国产精品资源在线 | 中文字幕一区二区三 | 欧美一区二区三区视频 | 美女毛片 | h视频免费观看 | 精品国产一区二区三区性色av | 欧美日韩国产一区二区三区在线观看 | 男人的天堂在线视频 | 亚洲一区二区三区高清 | 久草视频播放 | 国产伦精品一区二区三区四区视频 | 超碰人操| 久久精品亚洲一区二区 | 香蕉久久av一区二区三区 | 午夜精品一区二区三区在线观看 | 日本黄色一级片免费看 | 国产成人精品高清久久 | www.天天操.com | 久久久精 | 亚洲精品一二三区 | 欧美精品一区在线发布 | 夜夜视频| 午夜免费视频 | 伊人欧美视频 | 99成人| 污污视频免费网站 | 君岛美绪一区二区三区在线视频 | 精品免费 | 亚洲一区在线视频 | 精品国产乱码一区二区三区a | av中文字幕在线播放 | 一区二区在线免费观看 | 国偷自产av一区二区三区 | 国产精品乱码人人做人人爱 | 国产精品69久久久久水密桃 | 女人久久久久 | 国产免费自拍 | 九色国产 | 色黄网站 | 欧美精品一区二区蜜臀亚洲 | 午夜家庭影院 | 欧美日韩在线免费观看 | 国产综合精品一区二区三区 | 天天干天天搞天天射 | 久久久久久艹 | 亚洲一区二区三区免费 | 国产一级片免费观看 | 先锋资源中文字幕 | 成人久久18免费网站图片 | 中文字幕在线免费视频 | 亚洲国产精品久久 | 国产一区二区免费 | 久久中文字幕视频 | 午夜免费观看网站 | 亚洲一区二区中文字幕 | 天天干天操 | 亚洲综合影院 | 91成人免费看片 | 欧美男人的天堂 | 久久91视频| 一二三四在线视频观看社区 | 精品2区| 中文在线一区 | 免费一级在线观看 | 精品久久久久一区二区国产 | 最近的中文字幕在线看视频 | 九九在线视频 | 日韩一区二区三区福利视频 | 国产伦精品久久久一区二区三区 | 免费在线观看一级毛片 | 亚洲一区二区三区四区五区午夜 | 一区二区中文 | 国产网址在线 | 黄色a视频| 四虎影院免费网址 | 操到爽 | 日本理伦片午夜理伦片 | 日韩欧美精品区 | 亚洲夜幕久久日韩精品一区 | 欧美一区二区三区男人的天堂 | 黄色片在线免费观看 | 成人1区2区 | 国产成人综合在线 | 秋霞av电影 | 第一色视频 | 日韩乱码中文字幕 | 黄色一级片免费播放 | 国产剧情一区二区 | 国产一区二区免费 | 在线观看免费av网站 | 日本在线视频不卡 | 国产在线一区二区三区 | 一区二区精品 | 国产精品99久久久久久动医院 | 国产综合精品一区二区三区 | av中文在线 | www视频在线观看 | 国产ts余喵喵和直男多体位 | 成年免费视频 | 国产精品久久久久国产a级 91福利网站在线观看 | 国产成人精品亚洲777人妖 | 久久久久久免费视频 | 亚洲欧美综合精品久久成人 | 可以看黄的视频 | 亚洲成av人片在线观看无码 | 亚洲 精品 综合 精品 自拍 | 91亚洲视频在线观看 | 国产精品亚洲第一区在线暖暖韩国 | 久久久久久电影 | 91资源总站 | 综合久久综合 | 综合色九九 | 欧洲成人在线视频 | 免费xxxxx在线观看网站软件 | 日韩一区二区免费视频 | 成人在线一区二区 | 欧美精品导航 | 99视频在线免费观看 | 日韩在线一区二区 | 色爱区综合五月激情 | 久草热线视频 | 丁香久久| av一区在线 | 欧美黄色片免费观看 | 亚洲伊人久久综合 | 久久99久久久久 | wwwsihu| 国产精品一区亚洲二区日本三区 | 亚洲综合无码一区二区 | 国产一区二区精品 | 久久久久久久一区二区 | 免费不卡视频 | 亚洲精品视频在线免费 | 97在线观看 | 毛片久久久 | 91精品国产日韩91久久久久久 | 精品久久一区 | 美女精品视频在线 | 国产精品一区在线 | 99中文字幕 | 狠狠色狠狠色综合网 | www.一区二区 | 美女午夜影院 | 国产日韩欧美精品一区二区 | 欧美成人精品一区二区男人看 | 欧美日韩在线播放 | 久久青草av | 成人欧美一区二区三区白人 | 密室大逃脱第六季大神版在线观看 | 国产激情精品一区二区三区 | 一级欧美 | 久久婷婷色 | 欧美高潮 | 不卡中文一区 | 久久久久久国产一级毛片高清版 | 亚洲综合99 | av久久 | 久久久久久91亚洲精品中文字幕 | 国产精品成人一区二区三区夜夜夜 | 亚洲精品免费看 | 精品国产一区二区在线 | av片免费看 | 国产一区二区三区久久久久久久久 | 人人插人人 | 黄的视频网站 | 国产精品久久久久久一区二区三区 | 91最新 | 久草中文在线 | 日韩欧美在线综合 | 天天摸天天干 | 国产激情偷乱视频一区二区三区 | 欧美一级高潮片免费的 | 在线国产一区二区 | 视频一区二区国产 | 鲁一鲁影院 | 91中文字幕在线观看 | 黄色毛片在线看 | 亚洲天堂色2017 | 久久成人18免费网站 | 亚洲视频一区在线 | 成人在线观看免费 | 国产日韩高清在线 | 成av在线 | 欧美日韩中文字幕在线 | 日本一区二区三区四区 | 欧美精品在欧美一区二区少妇 | 中文字幕成人免费视频 | 日韩精品中文字幕在线观看 | 日本黄色一级电影 | 欧美日韩在线播放 | 九九在线视频 | 久久精品欧美 | 午夜影院在线观看 | 国产精品99久久免费观看 | 国产激情亚洲 | 国产一级二级毛片 | 精品久久久久久久久久久院品网 | 国产精品成人国产乱一区 | 欧美视频免费看 | 精品久久久久久久久久久久久久 | 国产精品久久久久久久久久东京 | 欧洲精品一区 | 一区二区日韩 | 国产最新视频 | 精品国产乱码久久久久久久软件 | 精久久久 | 中文字幕久久精品 | av男人天堂网 | 亚洲日日| 日本成人中文字幕 | 午夜寂寞影视在线观看 | 欧美日韩不卡合集视频 | 毛片免费在线 | 日韩中文字幕在线视频 | 校园春色av| 狠狠搞狠狠搞 | 九色av| 亚洲一区二区三区四区的 | 97影院在线午夜 | 青青草精品 | 国产乱肥老妇国产一区二 | 精品国产一区二区三区久久久蜜月 | 国产成人精品免高潮在线观看 | 噜噜噜噜噜在线视频 | 成人免费视频网站在线观看 | 久久精品中文字幕 | 国产成人99 | 国产激情影院 | 一区二区三区亚洲精品国 | 中文字幕日韩欧美 | 成人激情视频在线免费观看 | 91中文字幕在线观看 | 看亚洲a级一级毛片 | 黄色一级毛片免费 | 精品日韩一区二区 | 国产人久久人人人人爽 | 成人小视频在线观看 | 欧美一区亚洲二区 | 国产精品视频一区二区三区 | 亚洲一区中文字幕 | 中文字幕91| 国产精品久久久久国产a级 日韩在线二区 | 一区二区三区视频 | 波多野结衣一区二区三区四区 | 免费国产视频 | 久久久久久久久一区二区三区 | 特黄特黄视频 | 午夜免费视频 | 在线观看国产精品一区 | 午夜激情免费在线观看 | 99在线视频精品 | 国产精品视频入口 | 国产精品美女久久久久久久久久久 | 亚洲午夜精品一区二区三区 | 希岛爱理在线 | 久久精品国产亚洲 | 91亚洲视频在线观看 | 久久久久久麻豆 | 亚洲人成网站999久久久综合 | 欧美二区三区 | 国产精品资源在线 | 国产精品久久综合 | 欧美久久免费观看 | 国产精品久久久久久妇女6080 | 免费观看国产精品 | 久久国产精品一区二区三区 | 一区二区免费在线观看 | 国产真实精品久久二三区 | 操操日| 日韩高清一区二区 | 国产精品久久久久久一区二区三区 | 欧美与黑人午夜性猛交久久久 | 日韩三级在线免费 | 午夜国产羞羞视频免费网站 | 精品久久久久久国产 | 中文字幕乱码一区二区三区 | 欧美中文字幕在线 | 一级做a爰 | 久久精品国产清自在天天线 | 人人九九 | h片在线看| 欧美日韩视频在线第一区 | 黄色毛片免费看 | 99精品国产热久久91蜜凸 | 太平公主一级艳史播放高清 | 欧美一区二区三区精品 | 亚洲精品日本 | 一级毛片免费在线 | 精品影院| 欧美操穴 | 久久涩| 91高清视频| 成人在线观看免费视频 | 亚洲+变态+欧美+另类+精品 | 欧美自拍视频 | 精品国产精品三级精品av网址 | 精品国产精品 | 色免费视频| 亚洲黄色一区二区 | 天天操天天添 | 国产欧美精品一区二区三区四区 | 成人精品久久久 | 国产欧美精品一区 | 日韩婷婷| 久久网站免费视频 | 欧美一级片免费播放 | 欧美精品色网 | 亚洲首页 | 国产亚洲网站 | 美女h视频 | 欧美一极片 | 丝袜 亚洲 另类 欧美 综合 | 日韩在线亚洲 | 亚洲二区在线播放 | 国产一区2区| 天堂一区 | 国产人成精品一区二区三 | 精品国产乱码久久久久久1区2区 | 国产免费一区二区 | 国产精品热| 99精品欧美一区二区三区综合在线 | www精品| 亚洲国内精品 | 一级做a爰片毛片 | 中文字幕视频三区 | 日韩一区二区免费视频 | 天天射影院 | 综合二区 | gav成人免费播放视频 | 中文在线视频 | 国产综合区 | 狠狠综合久久av一区二区老牛 | 久久成人综合网 | 久久久久久久av | 黄色一级电影 | 国产美女精品 | 久久国产婷婷国产香蕉 | 青青久久 | 精品久久久蜜桃 | 波多野结衣在线网址 | 色婷婷综合久久久久中文一区二区 | 一区不卡 | 国产麻豆乱码精品一区二区三区 | 久久兔费看a级 | 精品久久久久香蕉网 | 精品在线看 | 欧美全黄 | 久久久久久久久久影院 | 国产一区二区精品在线 | 91精品视频在线 | 日韩三级电影在线免费观看 | 日韩精品一区二区三区在线观看 | 亚洲日日 | 一区二区三区在线播放 | 天堂男人在线 | 亚洲免费视频在线观看 | 97久久久国产精品 | 日韩国产欧美在线观看 | 1区在线| 欧美日韩国产中文字幕 | 国产日韩精品在线观看 | 婷婷在线视频 | 6080yy精品一区二区三区 | 搞黄视频在线观看 | 日韩久久精品一区二区 | 精品久久久久一区二区国产 | 久久9视频 | 亚洲色图88 | 99精品国产在热久久 | 看片国产 | 欧美黑人做爰xxxⅹ 国产精品一区二区视频 | 狠狠操夜夜操天天操 | 麻豆精品一区二区 | 午夜国产羞羞视频免费网站 | 蜜桃免费视频 | 在线免费国产 | 色精品 | 国产精品久久久久久久午夜片 | 欧美精品导航 | 少妇无套高潮一二三区 | 综合在线视频 | 自拍偷拍一区二区三区 | 激情综合在线 | 欧美成人黄色小说 | 欧美伦理电影一区二区 | 日韩欧美国产一区二区 | 亚洲欧洲一区二区 | 亚洲一区二区三区在线播放 | 日韩一区高清视频 | 中文字幕一区二区三 | 成人黄页在线观看 | 日本精品免费 | 午夜影院在线免费观看 | 久久精品无码一区二区日韩av | 欧美性猛交一区二区三区精品 | 日韩午夜视频在线观看 | 日韩中出 | 黄色手机在线观看 | 国产精品久久婷婷六月丁香 | 欧美精品一区二区三区蜜桃视频 | 亚洲欧美激情精品一区二区 | 久久成人国产 | 精品成人免费一区二区在线播放 | 久久久久a | 久久精品久久久久久 | 精品国产一区二区三区久久久蜜臀 | 国产精品成人品 | 亚洲人黄色片 | 欧洲亚洲精品久久久久 | 久久a国产| 久久精品一区二区三区四区 | 不卡一区 | 黄毛片视频 | 久久人人爽人人爽人人片亚洲 | 亚洲精品久久久久久一区二区 | 国产一区二区三区免费播放 | 一级黄色录像视频 | 性生活毛片 | 91国内外精品自在线播放 | 午夜精品影院 | 午夜在线观看免费 | 日韩在线观看一区 | 国产乱码精品一区二区三区av | 免费观看羞羞视频网站 | 伊人欧美在线 | 精品久久久久久久久久久久 | 中文字幕在线一区 | 毛片毛片毛片毛片毛片毛片 | 成人免费视频观看 | 国产欧美在线观看 | 国产精品18久久久 | 亚洲精选一区 | 国内久久| 精品国产鲁一鲁一区二区三区 | 欧洲美女7788成人免费视频 | 91短视频版在线观看www免费 | 亚洲福利在线观看 | 成人黄色av| 久久久久国产一区二区三区 | 天堂久久爱资源站www | 亚洲精品一区二三区 | 亚洲精品1区2区 | 亚洲精品一区久久久久久 | 久久99精品久久久久蜜臀 | 一区二区三区久久 | 狠狠亚洲 | 欧日韩免费视频 | 青青草久久网 | 欧美成人激情视频 | 午夜私人视频 | 一级毛片久久久 | 九九99 | 97国产精品视频人人做人人爱 | 国产精品一区亚洲二区日本三区 | 国产成人免费视频网站高清观看视频 | 草草视频免费 | 国产精品欧美一区二区三区 | 国产在线小视频 | 国产美女网站视频 | 人人爱人人草 | 亚洲一区二区福利 | 久久不卡日韩美女 | 91精品久久 | 99re在线观看视频 | 欧美精品久久久久久久久老牛影院 | 黄色一级片免费播放 | 午夜影院免费观看 | 欧美精品自拍 | 久久精品视频一区 | 国产高清在线精品 | 91麻豆精品国产91久久久资源速度 | 99综合在线 | 欧美视频中文字幕 | 日韩高清一区 | 亚洲激情一区二区 | 国产精品一品二区三区的使用体验 | 亚洲一区av | 91精品久久| 欧美jizzhd精品欧美巨大免费 | 五月香婷婷 | 色一情一乱一伦一区二区三区 | 国内自拍第一页 | 91精品国产一区二区三区香蕉 | 综合久久综合久久 | 色免费在线观看 | 亚洲综合区 | 男人的天堂视频网站 | 毛片免费在线观看 | 97av在线| 免费看的毛片 | 欧美一级免费 | 国内精品久久久久久影视8 91一区二区在线观看 | 国产精品成人国产乱一区 | 久久久久久久久免费视频 | 色综合天天综合网国产成人网 | 91精品久久久久久久99 | 国产精品25p | 国产在线在线 | 成人av免费在线观看 | 国产精品美女久久久久aⅴ国产馆 | a级毛片免费高清视频 | 精品永久 | 天天看片天天干 | 欧美成人综合 | 色噜噜视频 | 久草青青 | 中文字幕 视频一区 | 欧美精品欧美精品系列 | 国产欧美在线观看 | 久久狠狠| 欧美日韩精品久久久 | 亚洲婷婷一区 | 91精品久久 | 午夜亚洲 | 欧美亚洲一区 | 国产九九精品视频 | 精品久久精品 | 毛片一级片 | 久久视频一区 | 天天操网址 | 欧美日韩国产免费一区二区三区 | 欧美久久一区二区三区 | 欧美日韩在线看 | 午夜影院在线观看 | 亚洲爽爽| 精品中文一区 | 毛片a级片 | 亚洲国产婷婷 | 欧美日韩在线视频观看 | 久草视频在线播放 | 国产视频精品在线观看 | 男人久久天堂 | 在线免费观看色视频 | 99国产精品99久久久久久 | 国产伦精品一区二区三区在线 | 天堂av中文| 欧美白人做受xxxx视频 | 成人免费视频网站 | 久久久久久久久99精品 | 国产成人精品a视频一区www | 国产美女久久 | 亚洲精品国产电影 | 国产精品不卡 | 久久国产精品久久 | 国产精品一区在线观看 | 嫩草最新网址 | 中文字幕在线观看亚洲 | 岛国av一区 | 山外人精品 | 亚洲欧美日韩在线一区 | 欧美在线视频一区二区 | 羞羞视频在线免费观看 |