国产无遮挡裸体免费直播视频,久久精品国产蜜臀av,动漫在线视频一区二区,欧亚日韩一区二区三区,久艹在线 免费视频,国产精品美女网站免费,正在播放 97超级视频在线观看,斗破苍穹年番在线观看免费,51最新乱码中文字幕

Java并發(fā)源碼分析ConcurrentHashMap線程集合

 更新時(shí)間:2023年02月01日 15:16:49   作者:歷河川  
這篇文章主要為大家介紹了Java并發(fā)源碼分析ConcurrentHashMap線程集合,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

簡介

ConcurrentHashMap是一個(gè)線程安全的集合,底層是通過對(duì)指定索引位置上的節(jié)點(diǎn)進(jìn)行加鎖,而不是對(duì)整個(gè)數(shù)組加鎖,當(dāng)一個(gè)線程對(duì)指定索引位置上的節(jié)點(diǎn)加了鎖之后,其它線程就不能對(duì)該索引位置上的節(jié)點(diǎn)進(jìn)行操作,但可以對(duì)其它的索引位置上的節(jié)點(diǎn)操作,ConcurrentHashMap與HashMap有許多相似的地方,ConcurrentHashMap只是在一些產(chǎn)生線程安全的地方加了鎖,如果對(duì)HashMap了解的話,再來看ConcurrentHashMap就簡單許多。

常量

/** 數(shù)組最大容量大小(1073741824) */
private static final int MAXIMUM_CAPACITY = 1 << 30;
/** 默認(rèn)的初始容量大小 */
private static final int DEFAULT_CAPACITY = 16;
/** 默認(rèn)的并發(fā)等級(jí) */
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/** 負(fù)載因子 */
private static final float LOAD_FACTOR = 0.75f;
/** 鏈表轉(zhuǎn)換為紅黑樹的閾值 */
static final int TREEIFY_THRESHOLD = 8;
/** 紅黑樹轉(zhuǎn)換為鏈表的閾值 */
static final int UNTREEIFY_THRESHOLD = 6;
/**
 * 最小樹化閾值
 * 當(dāng)鏈表中的節(jié)點(diǎn)數(shù)量大于等于8時(shí),如果數(shù)組的長度小于最小樹化閾值則不會(huì)將鏈表轉(zhuǎn)換為紅黑樹,而是對(duì)數(shù)組進(jìn)行擴(kuò)容
 * 將鏈表中的節(jié)點(diǎn)分散到別的索引節(jié)點(diǎn)中去,只有當(dāng)數(shù)組的長度大于等于最小樹化閾值則會(huì)將鏈表轉(zhuǎn)換為紅黑樹
 */
static final int MIN_TREEIFY_CAPACITY = 64;
/** 擴(kuò)容時(shí),每個(gè)cpu最少需要負(fù)責(zé)的區(qū)域長度 */
private static final int MIN_TRANSFER_STRIDE = 16;
/** 擴(kuò)容時(shí)生成標(biāo)記的二進(jìn)制所在位置 */
private static int RESIZE_STAMP_BITS = 16;
/** 擴(kuò)容時(shí)記錄擴(kuò)容容量的標(biāo)記的二進(jìn)制所在位置 */
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//節(jié)點(diǎn)正在移動(dòng)
static final int MOVED = -1;
//節(jié)點(diǎn)已被樹化
static final int TREEBIN = -2;
static final int RESERVED = -3; // hash for transient reservations
//32位二進(jìn)制中正數(shù)最大的值,主要用于對(duì)key的hash值進(jìn)行二次運(yùn)算
static final int HASH_BITS = 0x7fffffff;
/** 當(dāng)前機(jī)器的cpu數(shù)量 */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
 * 存放元素的數(shù)組對(duì)象
 * 只有在第一次添加元素的時(shí)候進(jìn)行初始化
 * 如果沒有指定數(shù)組容量則使用默認(rèn)的容量16進(jìn)行初始化
 * 數(shù)組容量為2的次方,如果指定的數(shù)組容量不是2的次方則會(huì)進(jìn)行計(jì)算獲取到大于指定容量并是2的次方的數(shù)
 */
transient volatile Node<K, V>[] table;
/** 擴(kuò)容后的新數(shù)組,如果該數(shù)組不為空則說明當(dāng)前有其它線程正在進(jìn)行擴(kuò)容 */
private transient volatile Node<K, V>[] nextTable;
/** 基本計(jì)數(shù)器值,在沒有線程爭用的時(shí)候才會(huì)使用 */
private transient volatile long baseCount;
/**
 * -1時(shí)則說明數(shù)組對(duì)象正在初始化
 * 如果數(shù)組未被初始化時(shí)該值為數(shù)組初始化時(shí)的大小
 * 如果數(shù)組已被初始化該值為數(shù)組擴(kuò)容的閾值
 * -N:低16位二進(jìn)制轉(zhuǎn)換為十進(jìn)制數(shù)M,M-1則是當(dāng)前正在擴(kuò)容的線程數(shù)量
 */
private transient volatile int sizeCtl;
/**
 * 擴(kuò)容時(shí)需要轉(zhuǎn)移舊數(shù)組中的剩余的索引位置長度
 * 每個(gè)線程最少負(fù)責(zé)16個(gè)索引位置上的節(jié)點(diǎn)轉(zhuǎn)移
 */
private transient volatile int transferIndex;
/**
 * 計(jì)數(shù)器數(shù)組的鎖標(biāo)識(shí)
 * 用與初始化計(jì)數(shù)器數(shù)組以及擴(kuò)容和創(chuàng)建計(jì)數(shù)器對(duì)象的時(shí)候使用
 */
private transient volatile int cellsBusy;
/** 計(jì)數(shù)器數(shù)組 */
private transient volatile CounterCell[] counterCells;

構(gòu)造方法

/**
 * 創(chuàng)建一個(gè)默認(rèn)容量為16的數(shù)組對(duì)象
 * 該構(gòu)造方法中并沒有執(zhí)行任何操作
 * 并沒有創(chuàng)建默認(rèn)容量的數(shù)組對(duì)象
 * 只有在第一次添加元素的時(shí)候才會(huì)初始化數(shù)組對(duì)象
 */
public ConcurrentHashMap() {
}
/**
 * 創(chuàng)建一個(gè)指定初始容量的數(shù)組對(duì)象
 * @param initialCapacity 指定的初始容量
 */
public ConcurrentHashMap(int initialCapacity) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException();
    //校驗(yàn)指定的初始容量大小是否大于等于最大容量大小的一半
    //如果大于等于最大容量大小的一半則在后續(xù)第一次添加元素的時(shí)候使用最大容量大小創(chuàng)建數(shù)組
    //如果小于最大容量大小的一半則根據(jù)指定容量來計(jì)算最接近指定容量大小并大于指定容量的2的次方
    //此處有一個(gè)疑問,指定容量大小如果等于最大容量大小的一半則說明指定容量大小是2的29次方,為什么不使用指定容量大小,而是使用最大的容量大小?
    //從tableSizeFor方法也能看出,如果指定的初始容量大小本來就是2的次方的話并不會(huì)使用指定的初始容量大小來創(chuàng)建數(shù)組對(duì)象
    //而是使用大于指定初始容量大小的2的次方來創(chuàng)建數(shù)組對(duì)象,比如指定的初始容量大小為16,那就會(huì)使用32來創(chuàng)建數(shù)組對(duì)象
    //在HashMap中如果指定的初始容量大小本來就是2的次方的話則會(huì)使用指定的初始容量大小來創(chuàng)建數(shù)組對(duì)象
    //并不會(huì)像ConcurrentHashMap一樣會(huì)取比當(dāng)前指定的2的次方的初始容量大小大的2的次方的容量大小來創(chuàng)建數(shù)組對(duì)象
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
            MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    //將計(jì)算后的容量大小賦值給sizeCtl等待初始化
    this.sizeCtl = cap;
}
/**
 * 根據(jù)指定的集合中的元素來創(chuàng)建新的數(shù)組對(duì)象
 * @param m the map
 */
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
    //使用默認(rèn)的初始容量大小等待初始化
    this.sizeCtl = DEFAULT_CAPACITY;
    //初始化新的數(shù)組對(duì)象并將指定集合中的元素添加到新的數(shù)組對(duì)象中
    putAll(m);
}
/**
 * 根據(jù)指定的初始容量大小和指定的負(fù)載因子來創(chuàng)建新的數(shù)組對(duì)象
 */
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, 1);
}
/**
 * 根據(jù)指定的初始容量大小和指定的負(fù)載因子和并發(fā)數(shù)來創(chuàng)建新的數(shù)組對(duì)象
 */
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    if (initialCapacity < concurrencyLevel)
        //初始容量大小小于并發(fā)數(shù)則使用并發(fā)數(shù)來創(chuàng)建數(shù)組對(duì)象
        initialCapacity = concurrencyLevel;
    //通過計(jì)算獲取到擴(kuò)容的閾值
    long size = (long) (1.0 + (long) initialCapacity / loadFactor);
    //如果閾值大于等于最大容量大小則使用最大容量大小來創(chuàng)建新的數(shù)組對(duì)象
    //如果閾值小于最大容量大小則使用閾值來計(jì)算獲取到大于閾值并是2的次方的值
    //使用計(jì)算后的值來創(chuàng)建新的數(shù)組對(duì)象
    int cap = (size >= (long) MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int) size);
    //將計(jì)算后的容量大小賦值給sizeCtl等待初始化
    this.sizeCtl = cap;
}

在構(gòu)造方法中并沒有初始化數(shù)組對(duì)象,而是在第一次添加元素的時(shí)候才會(huì)進(jìn)行初始化,根據(jù)指定的初始容量大小和指定的負(fù)載因子進(jìn)行初始化,如果沒有指定初始容量或負(fù)載因子則使用默認(rèn)的,而數(shù)組的大小必須是2的次方,最大的數(shù)組大小則是2的30次方,如果大于2的30次方的話,數(shù)組則不會(huì)將繼續(xù)進(jìn)行擴(kuò)容,當(dāng)數(shù)組的容量大小已到達(dá)最大時(shí),后續(xù)添加的元素則會(huì)掛載到鏈表或紅黑樹上,如果說指定的初始容量不是2的次方時(shí)則會(huì)調(diào)用tableSizeFor方法計(jì)算出大于指定初始容量并且是2的次方的值,如果說指定的初始容量本身就是2的次方時(shí)通過計(jì)算出的值還是本身。

此處就有一個(gè)疑問,本身的值不是2的次方的時(shí)候會(huì)通過計(jì)算獲取到大于指定的初始容量并且是最接近指定的初始容量的2的次方的值,如果本身的值就是2的次方的時(shí)候則會(huì)取本身的值,但是在第二個(gè)構(gòu)造方法中的tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1))方法是有問題的,假如initialCapacity 為16,按理來說最終數(shù)組初始化的容量大小應(yīng)該也為16,但是經(jīng)過該方法計(jì)算最終的數(shù)組初始化的容量大小為32,雖然該構(gòu)造方法有問題,但是并不影響。

put

public V put(K key, V value) {
    return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null)
        //key和value不能為空
        throw new NullPointerException();
    //將key的hash值的高16位二進(jìn)制參加運(yùn)算獲取到新的hash值
    int hash = spread(key.hashCode());
    //指定索引位置上節(jié)點(diǎn)的數(shù)量
    int binCount = 0;
    for (Node<K, V>[] tab = table; ; ) {
        //數(shù)組中指定索引的節(jié)點(diǎn)
        Node<K, V> f;
        //n 數(shù)組長度
        //i key所在的數(shù)組索引位置
        //fh 指定索引的節(jié)點(diǎn)的hash值
        int n, i, fh;
        //校驗(yàn)數(shù)組是否已經(jīng)初始化
        if (tab == null || (n = tab.length) == 0)
            //數(shù)組未初始化則初始化數(shù)組
            //并返回初始化完成的數(shù)組對(duì)象
            tab = initTable();
        //通過數(shù)組索引長度與key的hash值進(jìn)行與運(yùn)算獲取到指定索引位置上的元素節(jié)點(diǎn),并校驗(yàn)元素節(jié)點(diǎn)是否為空
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //指定索引位置上的節(jié)點(diǎn)不存在則使用傳遞進(jìn)來的key和value創(chuàng)建新的節(jié)點(diǎn)
            //并將新的節(jié)點(diǎn)放置到指定索引位置上
            if (casTabAt(tab, i, null,new Node<K, V>(hash, key, value, null)))
                //節(jié)點(diǎn)添加成功則退出循環(huán)
                break;
        //校驗(yàn)索引位置上的節(jié)點(diǎn)是否正在移動(dòng)
        } else if ((fh = f.hash) == MOVED)
            //如果索引位置上的節(jié)點(diǎn)正在移動(dòng)則幫忙移動(dòng)節(jié)點(diǎn)到新的數(shù)組中
            //協(xié)助擴(kuò)容
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            //使用鎖鎖住指定索引位置上的節(jié)點(diǎn)
            //這樣其它線程就不能對(duì)該索引位置上的節(jié)點(diǎn)進(jìn)行操作
            //其它線程可以對(duì)其它索引位置上的節(jié)點(diǎn)進(jìn)行操作
            synchronized (f) {
                //校驗(yàn)指定索引位置上的節(jié)點(diǎn)是否被更改
                if (tabAt(tab, i) == f) {
                    //校驗(yàn)節(jié)點(diǎn)的hash值是否大于等于0
                    //如果節(jié)點(diǎn)的hash值大于等于0則說明該索引位置上只有一個(gè)節(jié)點(diǎn)或節(jié)點(diǎn)是一個(gè)鏈表節(jié)點(diǎn)
                    if (fh >= 0) {
                        binCount = 1;
                        //從指定索引位置上的節(jié)點(diǎn)開始遍歷
                        for (Node<K, V> e = f; ; ++binCount) {
                            K ek;
                            //校驗(yàn)遍歷到的節(jié)點(diǎn)的hash值以及key是否與傳遞的key和計(jì)算的hash值相同
                            //如果相同則根據(jù)onlyIfAbsent參數(shù)來決定是否需要使用新的value替換舊的value
                            if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                                //獲取舊的value
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    //使用新的value替換舊的value
                                    e.val = value;
                                break;
                            }
                            //遍歷到的節(jié)點(diǎn)的hash值以及key不相同
                            //則判斷當(dāng)前遍歷到的節(jié)點(diǎn)是否是鏈表上的最后一個(gè)節(jié)點(diǎn)
                            //如果不是最后一個(gè)節(jié)點(diǎn)則繼續(xù)遍歷
                            //如果是最后一個(gè)節(jié)點(diǎn)則為key和value創(chuàng)建一個(gè)新的節(jié)點(diǎn)
                            //并將原尾節(jié)點(diǎn)的next指針指向新創(chuàng)建的節(jié)點(diǎn)
                            Node<K, V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K, V>(hash, key, value, null);
                                break;
                            }
                        }
                     //校驗(yàn)節(jié)點(diǎn)是否是一個(gè)紅黑樹節(jié)點(diǎn)
                    } else if (f instanceof TreeBin) {
                        Node<K, V> p;
                        binCount = 2;
                        //將指定的key和value封裝成一個(gè)樹節(jié)點(diǎn)并添加到紅黑樹中
                        //如果紅黑樹中已經(jīng)存在相同key的節(jié)點(diǎn)則根據(jù)onlyIfAbsent參數(shù)來決定是否使用新的value替換紅黑樹中的節(jié)點(diǎn)的value
                        if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key,value)) != null) {
                            //獲取節(jié)點(diǎn)中的舊值
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                //替換節(jié)點(diǎn)中的value
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                //校驗(yàn)索引位置上的節(jié)點(diǎn)的數(shù)量是否到達(dá)樹化的閾值
                //如果該索引位置上的節(jié)點(diǎn)本來就是樹節(jié)點(diǎn)則不會(huì)繼續(xù)樹化
                //只有當(dāng)索引位置上的節(jié)點(diǎn)是鏈表節(jié)點(diǎn)并且節(jié)點(diǎn)的數(shù)量大于等于8的時(shí)候才會(huì)樹化
                if (binCount >= TREEIFY_THRESHOLD)
                    //索引位置上的節(jié)點(diǎn)數(shù)量已經(jīng)到達(dá)樹化的閾值
                    //則將該索引位置上的所有節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    //返回被替換的舊值
                    return oldVal;
                break;
            }
        }
    }
    //更新集合中元素節(jié)點(diǎn)的數(shù)量,并校驗(yàn)是否需要擴(kuò)容
    addCount(1L, binCount);
    return null;
}

其實(shí)putval中的邏輯并不難,主要還是putval中調(diào)用的其它一些方法比較難以理解,我們就一步步的來看。

首先如果keyvaluenull的情況下是不能添加到當(dāng)前集合中的,再看spread方法,該方法是將key本身的hash值的高16位參加運(yùn)算獲取到新的hash值,如果數(shù)組的長度的二進(jìn)制是在低16位二進(jìn)制中就會(huì)導(dǎo)致key高16位的二進(jìn)制沒有參加運(yùn)算,容易導(dǎo)致運(yùn)算后的key的索引位置發(fā)生沖突,所以需要將高16位二進(jìn)制無符號(hào)右移16位與原hash值的二進(jìn)制進(jìn)行運(yùn)算減少索引沖突。

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

為什么要與HASH_BITS進(jìn)行與運(yùn)算呢? HASH_BITS是32位二進(jìn)制中最大的正整數(shù),與HASH_BITS進(jìn)行與運(yùn)算其實(shí)就是讓計(jì)算后的hash值保持在正整數(shù)。

后續(xù)整個(gè)代碼都被for循環(huán)包裹著,只有當(dāng)添加元素或替換元素成功時(shí)才會(huì)退出,而for循環(huán)里總共有4個(gè)大的分支。

1.數(shù)組未初始化,此時(shí)就需要調(diào)用initTable方法初始化數(shù)組對(duì)象,之前也說過數(shù)組對(duì)象是在第一次添加元素的時(shí)候初始化的,這是一種懶加載的思想,當(dāng)你使用到的時(shí)候才會(huì)去初始化。

initTable

private final Node<K, V>[] initTable() {
    Node<K, V>[] tab;
    int sc;
    //校驗(yàn)數(shù)組是否未被初始化
    while ((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) < 0)
            //數(shù)組長度小于0則說明其它線程正在準(zhǔn)備初始化數(shù)組
            //當(dāng)前線程則需要讓出cpu讓其它線程初始化數(shù)組
            Thread.yield();
        //通過cas操作將sizeCtl修改成-1,告知其它線程當(dāng)前線程正在準(zhǔn)備初始化數(shù)組
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    //如果指定了初始容量則使用指定的初始容量來創(chuàng)建數(shù)組
                    //如果沒有指定則使用默認(rèn)的初始容量大小16來創(chuàng)建數(shù)組
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    //初始化數(shù)組
                    @SuppressWarnings("unchecked")
                    Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];
                    //將初始化的數(shù)組對(duì)象賦值給當(dāng)前集合中的數(shù)組對(duì)象
                    //并賦值給tab,下一次循環(huán)校驗(yàn)則會(huì)發(fā)現(xiàn)tab不為空則會(huì)退出循環(huán)并退出當(dāng)前方法
                    table = tab = nt;
                    //計(jì)算數(shù)組擴(kuò)容的閾值
                    sc = n - (n >>> 2);
                }
            } finally {
                //如果try中的代碼塊執(zhí)行失敗時(shí)sizeCtl則是原先數(shù)組的長度
                //執(zhí)行成功時(shí)sizeCtl則是擴(kuò)容的閾值
                sizeCtl = sc;
            }
            break;
        }
    }
    //數(shù)組已被初始化返回?cái)?shù)組
    return tab;
}

我們來看initTable方法中的代碼,首先whlie循環(huán)中校驗(yàn)了數(shù)組是否還沒被初始化,當(dāng)數(shù)組未被初始化的時(shí)候,當(dāng)前線程則會(huì)去嘗試初始化數(shù)組,直到數(shù)組被當(dāng)前線程或者其它線程初始化成功。

先看(sc = sizeCtl) < 0這段代碼,sizeCtl小于0分為兩種情況,sizeCtl為-1的時(shí)候則說明當(dāng)前有其它線程正在初始化數(shù)組對(duì)象,sizeCtl小于-1的時(shí)候則說明當(dāng)前數(shù)組正在進(jìn)行擴(kuò)容,從當(dāng)前情況來看數(shù)組正在進(jìn)行擴(kuò)容的這種情況是不大可能存在的,所以說只有可能其它線程正在進(jìn)行初始化數(shù)組,當(dāng)其它線程正在初始化數(shù)組對(duì)象時(shí),那當(dāng)前線程則需要讓出cpu的執(zhí)行權(quán),等待初始化數(shù)組對(duì)象的線程執(zhí)行完成。

再看后一個(gè)判斷,該判斷是一個(gè)cas操作,將sizeCtl修改成-1,告知其它線程當(dāng)前線程正在初始化數(shù)組,如果當(dāng)前線程修改失敗,則說明有其它線程搶先修改了sizeCtl的值,那當(dāng)前線程則需要重新走while循環(huán)來查看數(shù)組是否初始化完成,如果沒有完成那需要讓出cpu的執(zhí)行權(quán),如果數(shù)組已經(jīng)被其它線程初始化完成,那當(dāng)前線程則可以對(duì)數(shù)組進(jìn)行操作,如果說當(dāng)前線程執(zhí)行cas操作成功,則會(huì)先查看是否指定了數(shù)組初始的容量大小,如果指定了則使用指定的初始容量大小來創(chuàng)建數(shù)組對(duì)象,如果沒有指定則使用默認(rèn)的初始容量大小16來創(chuàng)建數(shù)組對(duì)象,該指定的初始容量并非是真正的初始容量,如果說指定的初始容量是2的次方則會(huì)使用該容量大小來創(chuàng)建數(shù)組對(duì)象,如果說指定的初始容量不是2的次方的時(shí)候則會(huì)通過計(jì)算來獲取大于這個(gè)指定的初始容量并且是最接近該初始容量的2的次方,并使用計(jì)算后的值來初始化數(shù)組對(duì)象,當(dāng)初始化完成數(shù)組對(duì)象時(shí)則會(huì)計(jì)算下一次數(shù)組擴(kuò)容的閾值。

initTable方法中我們看到代碼中使用一個(gè)變量sizeCtl來區(qū)分多種情況,其實(shí)sizeCtl一共分為4種情況,那我們先了解一下分為那4種。

  • sizeCtl = N && table = null:當(dāng)數(shù)組還未被初始化的時(shí)候,sizeCtl則是數(shù)組初始化的容量大小
  • sizeCtl = N:當(dāng)數(shù)組已經(jīng)被初始化了的時(shí)候,sizeCtl則是數(shù)組下一次擴(kuò)容的閾值
  • sizeCtl = -1:說明當(dāng)前數(shù)組對(duì)象正在被初始化
  • sizeCtl = -N:當(dāng)前數(shù)組正在進(jìn)行擴(kuò)容,-N的低16位二進(jìn)制轉(zhuǎn)換為十進(jìn)制數(shù)M,M-1則是當(dāng)前正在進(jìn)行擴(kuò)容的線程數(shù)量,-N的高16位二進(jìn)制則是本次擴(kuò)容的標(biāo)記

2.通過spread方法計(jì)算后的hash值與數(shù)組索引長度進(jìn)行與運(yùn)算獲取到當(dāng)前元素節(jié)點(diǎn)所在的索引位置,并調(diào)用tabAt方法根據(jù)索引位置計(jì)算該索引中的元素節(jié)點(diǎn)在內(nèi)存中的偏移量,再根據(jù)偏移量從table數(shù)組中獲取元素節(jié)點(diǎn),如果該元素節(jié)點(diǎn)為空則根據(jù)指定的keyvalue創(chuàng)建一個(gè)新的元素節(jié)點(diǎn)并調(diào)用casTabAt方法根據(jù)cas操作將新的元素節(jié)點(diǎn)添加到數(shù)組中指定的索引位置上。

static {
    try {
        U = sun.misc.Unsafe.getUnsafe();
        Class<?> k = ConcurrentHashMap.class;
        SIZECTL = U.objectFieldOffset
                (k.getDeclaredField("sizeCtl"));
        TRANSFERINDEX = U.objectFieldOffset
                (k.getDeclaredField("transferIndex"));
        BASECOUNT = U.objectFieldOffset
                (k.getDeclaredField("baseCount"));
        CELLSBUSY = U.objectFieldOffset
                (k.getDeclaredField("cellsBusy"));
        Class<?> ck = CounterCell.class;
        CELLVALUE = U.objectFieldOffset
                (ck.getDeclaredField("value"));
        Class<?> ak = Node[].class;
        //獲取數(shù)組中第一個(gè)元素在內(nèi)存中開始的偏移量
        ABASE = U.arrayBaseOffset(ak);
        //獲取數(shù)組中元素在內(nèi)存中的增量地址
        int scale = U.arrayIndexScale(ak);
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        //獲取增量地址在32位二進(jìn)制情況下高位連續(xù)包含0的個(gè)數(shù)
        //使用31減去包含0的個(gè)數(shù)獲取到需要位移的次數(shù)
        ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
    } catch (Exception e) {
        throw new Error(e);
    }

在看tabAt方法和casTabAt方法之前,我們先了解一下ConcurrentHashMap中靜態(tài)代碼塊中的一些代碼,通過getUnsafe()方法獲取到Unsafe類,Unsafe類是一個(gè)不安全的類,能通過該類直接對(duì)內(nèi)存中的一些資源進(jìn)行操作,U.objectFieldOffset方法能獲取到指定名稱變量在內(nèi)存中的偏移量,后續(xù)能根據(jù)該偏移量直接對(duì)變量進(jìn)行操作,U.arrayBaseOffset方法獲取指定類型數(shù)組中第一個(gè)元素在內(nèi)存中的偏移量,ABASE則是數(shù)組中第一個(gè)元素在內(nèi)存中開始的偏移量,U.arrayIndexScale方法獲取指定類型數(shù)組中元素在內(nèi)存中的增量地址,ASHIFT則是通過增量地址在32位二進(jìn)制的情況下高位連續(xù)為0的個(gè)數(shù),并使用31減去連續(xù)為0的個(gè)數(shù)獲取到需要位移的次數(shù)。

tabAt

static final <K, V> Node<K, V> tabAt(Node<K, V>[] tab, int i) {
    //根據(jù)指定的索引位置計(jì)算該索引中的節(jié)點(diǎn)在內(nèi)存中的偏移量
    //并從tab數(shù)組中根據(jù)索引所在的內(nèi)存的偏移量獲取節(jié)點(diǎn)
    return (Node<K, V>) U.getObjectVolatile(tab, ((long) i << ASHIFT) + ABASE);
}

i << ASHIFT獲取到第一個(gè)索引位置到索引i的增量地址,再加上ABASE即可獲取到索引i在內(nèi)存中的偏移量,然后在根據(jù)偏移量從volatile類型的tab數(shù)組中獲取節(jié)點(diǎn)。

casTabAt

static final <K, V> boolean casTabAt(Node<K, V>[] tab, int i,Node<K, V> c, Node<K, V> v) {
    //根據(jù)指定的索引位置計(jì)算該索引中的節(jié)點(diǎn)在內(nèi)存中的偏移量
    //并根據(jù)偏移量獲取節(jié)點(diǎn),并將獲取到的節(jié)點(diǎn)與傳遞的節(jié)點(diǎn)c進(jìn)行比較
    //如果兩個(gè)節(jié)點(diǎn)相同則使用節(jié)點(diǎn)v進(jìn)行替換
    return U.compareAndSwapObject(tab, ((long) i << ASHIFT) + ABASE, c, v);
}

根據(jù)指定的索引位置的內(nèi)存偏移量獲取節(jié)點(diǎn),如果獲取到的節(jié)點(diǎn)與傳遞的節(jié)點(diǎn)c相同則使用節(jié)點(diǎn)v進(jìn)行替換,該方法用于添加一個(gè)新的節(jié)點(diǎn)或替換節(jié)點(diǎn),添加新的節(jié)點(diǎn)的時(shí)候,只需要將節(jié)點(diǎn)c設(shè)置為null,替換節(jié)點(diǎn)的時(shí)候,節(jié)點(diǎn)c則需要設(shè)置為原索引位置上的節(jié)點(diǎn)。

3.當(dāng)指定索引位置上的節(jié)點(diǎn)不為null的時(shí)候,但是節(jié)點(diǎn)的hash值為-1,此時(shí)說明有其它線程正在對(duì)數(shù)組進(jìn)行擴(kuò)容,并且指定索引位置上的節(jié)點(diǎn)已經(jīng)轉(zhuǎn)移到了新的數(shù)組中,此時(shí)當(dāng)前線程就需要調(diào)用helpTransfer方法協(xié)助其它線程進(jìn)行擴(kuò)容,當(dāng)協(xié)助完成時(shí)則會(huì)對(duì)新的數(shù)組進(jìn)行操作。

helpTransfer

final Node<K, V>[] helpTransfer(Node<K, V>[] tab, Node<K, V> f) {
    Node<K, V>[] nextTab;
    int sc;
    //f instanceof ForwardingNode 當(dāng)前節(jié)點(diǎn)正在轉(zhuǎn)移
    //nextTab = ((ForwardingNode<K, V>) f).nextTable) != null 擴(kuò)容的新數(shù)組是否不為空
    //如果條件都為true則說明有線程正在擴(kuò)容中,當(dāng)前線程則需要協(xié)助擴(kuò)容
    if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K, V>) f).nextTable) != null) {
        //擴(kuò)容標(biāo)記
        int rs = resizeStamp(tab.length);
        while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) {
            //(sc >>> RESIZE_STAMP_SHIFT) != rs 校驗(yàn)擴(kuò)容標(biāo)識(shí)是否一致,不一致則說明不是同一個(gè)數(shù)組
            //sc == rs + 1 應(yīng)該是sc == (rs << 16) + 1 校驗(yàn)擴(kuò)容是否完成
            //sc == rs + MAX_RESIZERS 應(yīng)該是sc == (rs << 16) + MAX_RESIZERS 校驗(yàn)擴(kuò)容的線程的數(shù)量是否到達(dá)最大
            //transferIndex <= 0 舊數(shù)組中還有多少?zèng)]有轉(zhuǎn)移的索引節(jié)點(diǎn)長度,如果小于等于0則說明已經(jīng)轉(zhuǎn)移完成則不需要協(xié)助
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0)
                break;
            //擴(kuò)容線程加1
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                //協(xié)助擴(kuò)容
                transfer(tab, nextTab);
                break;
            }
        }
        //擴(kuò)容完成則返回新的數(shù)組,對(duì)新的數(shù)組進(jìn)行操作
        return nextTab;
    }
    //當(dāng)前節(jié)點(diǎn)還未被轉(zhuǎn)移或新的數(shù)組還沒有被初始化則對(duì)舊數(shù)組進(jìn)行操作
    return table;
}

我們來看一下協(xié)助擴(kuò)容的方法,局部變量nextTabsc則是最新的擴(kuò)容數(shù)組和擴(kuò)容標(biāo)識(shí)以及擴(kuò)容的線程數(shù)量。

  • tab != null和f instanceof FowardingNode:該條件只是為了代碼的健壯性,tab !=null從代碼來看,既然當(dāng)前線程執(zhí)行到了協(xié)助擴(kuò)容的方法,那tab肯定是不為null的,再看f instanceof FowardingNode條件,在之前的判斷中f的節(jié)點(diǎn)的hash值為-1,-1就已經(jīng)代表節(jié)點(diǎn)已經(jīng)被轉(zhuǎn)移,只有當(dāng)節(jié)點(diǎn)已經(jīng)轉(zhuǎn)移的時(shí)候才會(huì)執(zhí)行到當(dāng)前方法中,此時(shí)你就可以發(fā)現(xiàn)之前為什么要將節(jié)點(diǎn)的hash值控制在正整數(shù),如果說沒有控制在正整數(shù)的時(shí)候,當(dāng)節(jié)點(diǎn)的hash值恰巧為-1的時(shí)候,就會(huì)導(dǎo)致線程執(zhí)行一些沒有必要的代碼,而且在后續(xù)也不好區(qū)分鏈表節(jié)點(diǎn)。
  • (nextTab = ((ForwardingNode<K, V>) f).nextTable) != null:該條件則是獲取最新的擴(kuò)容數(shù)組并校驗(yàn)最新的數(shù)組是否不為null。

當(dāng)上述條件都為true的時(shí)候則會(huì)通過resizeStamp方法使用舊數(shù)組的長度生成一個(gè)擴(kuò)容標(biāo)識(shí),再看while循環(huán)中的條件語句。

nextTab == nextTable:校驗(yàn)當(dāng)前線程協(xié)助擴(kuò)容的數(shù)組是否是最新的數(shù)組,如果此時(shí)nextTable == null的話則說明已經(jīng)完成了擴(kuò)容,那當(dāng)前線程則不需要協(xié)助擴(kuò)容了,只需要執(zhí)行自己該執(zhí)行的操作,如果說當(dāng)前線程協(xié)助擴(kuò)容的數(shù)組不是最新的擴(kuò)容數(shù)組的時(shí)候,則會(huì)從nextTable中繼續(xù)獲取f節(jié)點(diǎn),再從f節(jié)點(diǎn)中獲取nextTable,繼續(xù)校驗(yàn)是否是最新的擴(kuò)容數(shù)組。

table == tab:校驗(yàn)擴(kuò)容是否完成。

(sc = sizeCtl) < 0:校驗(yàn)是否正在擴(kuò)容中。

上述條件都為true的時(shí)候則說明數(shù)組正在進(jìn)行擴(kuò)容中,我們先來看一些在哪些條件下當(dāng)前線程不會(huì)去協(xié)助擴(kuò)容。

(sc >>> RESIZE_STAMP_SHIFT) != rs:校驗(yàn)擴(kuò)容標(biāo)識(shí)是否一致,不一致則說明不是對(duì)同一長度的數(shù)組進(jìn)行的擴(kuò)容。

sc == rs + 1:該條件應(yīng)該是sc == (rs << 16) + 1,校驗(yàn)擴(kuò)容是否完成,在擴(kuò)容開始的時(shí)候sizeCtl為(rs << 16) + 2,這個(gè)2代表著開始擴(kuò)容的線程以及最后一個(gè)擴(kuò)容完成的線程,在transfer擴(kuò)容方法中,每一個(gè)線程完成了所否則的區(qū)域的時(shí)候都會(huì)將sizeCtl的值減1,當(dāng)最后一個(gè)線程執(zhí)行完成時(shí)減1,此時(shí)sizeCtl的值就等于(rs<< 16) + 1則說明整個(gè)數(shù)組擴(kuò)容都已經(jīng)完成了,已經(jīng)不需要線程協(xié)助了。

sc == rs + MAX_RESIZERS:該條件應(yīng)該是sc == (rs << 16) + MAX_RESIZERS,校驗(yàn)當(dāng)前正在擴(kuò)容的線程的數(shù)量是否到達(dá)最大值(65535)。

transferIndex <= 0:校驗(yàn)舊數(shù)組中還未完成轉(zhuǎn)移的索引節(jié)點(diǎn)長度是否小于等于0,小于等于0則說明已經(jīng)完成了轉(zhuǎn)移則不需要協(xié)助。

當(dāng)上述條件都不成立的時(shí)候則說明數(shù)組擴(kuò)容需要協(xié)助,此時(shí)通過cas操作將擴(kuò)容線程的數(shù)量加1并調(diào)用transfer方法來協(xié)助擴(kuò)容,該擴(kuò)容方法放在后面講解。

4.當(dāng)上面3個(gè)分支都不成立的時(shí)候,則說明該索引位置上的節(jié)點(diǎn)不為空并且沒有被轉(zhuǎn)移,此時(shí)就需要使用synchronized對(duì)指定索引位置上的節(jié)點(diǎn)加鎖,然后根據(jù)節(jié)點(diǎn)的類型來執(zhí)行相關(guān)的操作。

  • 鏈表節(jié)點(diǎn):鏈表節(jié)點(diǎn)的hash值都是大于等于0的,當(dāng)指定索引位置上的節(jié)點(diǎn)是一個(gè)鏈表節(jié)點(diǎn)的時(shí)候,則會(huì)從鏈表的頭節(jié)點(diǎn)開始遍歷并且記錄鏈表中節(jié)點(diǎn)的數(shù)量,當(dāng)鏈表中的節(jié)點(diǎn)的key與指定的key相同則根據(jù)onlyIfAbsent參數(shù)來決定是否需要替換value,如果沒有相同的key時(shí),則會(huì)為指定的keyvalue創(chuàng)建一個(gè)新的節(jié)點(diǎn),并將新的節(jié)點(diǎn)添加到鏈表的尾部。
  • 紅黑樹節(jié)點(diǎn):紅黑樹的頭節(jié)點(diǎn)的hash值固定為-2,此處直接校驗(yàn)的是頭節(jié)點(diǎn)的類型是否是紅黑樹,當(dāng)指定索引位置上的頭節(jié)點(diǎn)是一個(gè)紅黑樹節(jié)點(diǎn),則會(huì)調(diào)用頭節(jié)點(diǎn)的putTreeVal方法將指定的keyvalue添加到紅黑樹中,如果說指定的key已經(jīng)存在在紅黑樹中則會(huì)根據(jù)onlyIfAbsent參數(shù)來決定是否需要替換value,此處為什么是調(diào)用頭節(jié)點(diǎn)的putTreeVal方法呢?看到后面鏈表轉(zhuǎn)換為紅黑樹節(jié)點(diǎn)的時(shí)候就能明白了。

putTreeVal

final TreeNode<K, V> putTreeVal(int h, K k, V v) {
    Class<?> kc = null;
    boolean searched = false;
    for (TreeNode<K, V> p = root; ; ) {
        int dir, ph;
        K pk;
        if (p == null) {
            first = root = new TreeNode<K, V>(h, k, v, null, null);
            break;
        } else if ((ph = p.hash) > h)
            dir = -1;
        else if (ph < h)
            dir = 1;
        else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
            return p;
        else if ((kc == null &&
                (kc = comparableClassFor(k)) == null) ||
                (dir = compareComparables(kc, k, pk)) == 0) {
            if (!searched) {
                TreeNode<K, V> q, ch;
                searched = true;
                if (((ch = p.left) != null &&
                        (q = ch.findTreeNode(h, k, kc)) != null) ||
                        ((ch = p.right) != null &&
                                (q = ch.findTreeNode(h, k, kc)) != null))
                    return q;
            }
            dir = tieBreakOrder(k, pk);
        }
        TreeNode<K, V> xp = p;
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            TreeNode<K, V> x, f = first;
            first = x = new TreeNode<K, V>(h, k, v, f, xp);
            if (f != null)
                //將新添加的樹節(jié)點(diǎn)設(shè)置為鏈表的頭節(jié)點(diǎn)
                f.prev = x;
            if (dir <= 0)
                xp.left = x;
            else
                xp.right = x;
            if (!xp.red)
                x.red = true;
            else {
                //獲取寫鎖,如果獲取寫鎖失敗則會(huì)將當(dāng)前線程掛起進(jìn)行等待
                //如果有線程正在讀,那當(dāng)前寫的線程就要掛起進(jìn)行等待
                //當(dāng)讀的線程執(zhí)行完成之后就會(huì)去喚醒掛起的線程
                //如果說當(dāng)前線程在寫,有線程準(zhǔn)備讀
                //那讀線程并不會(huì)去讀取紅黑樹中的節(jié)點(diǎn)
                //而是讀取鏈表的節(jié)點(diǎn),因?yàn)門reeBin對(duì)象中包含了紅黑樹的根節(jié)點(diǎn)并且也包含了鏈表的頭節(jié)點(diǎn)
                //如果發(fā)生寫寫的問題呢?
                //其實(shí)并不會(huì)發(fā)生寫寫的問題,因?yàn)楫?dāng)前整個(gè)方法都被外部的synchronized鎖住了
                lockRoot();
                try {
                    //平衡紅黑樹中的節(jié)點(diǎn)
                    root = balanceInsertion(root, x);
                } finally {
                    //釋放鎖
                    unlockRoot();
                }
            }
            break;
        }
    }
    assert checkInvariants(root);
    return null;
}

putTreeValbalanceInsertion方法在之前的HashMap中已經(jīng)講過,此處就不再講解,我們主要還是看一下lockRootunlockRoot方法。

鎖狀態(tài)

//鎖狀態(tài)
volatile int lockState;
//寫鎖
static final int WRITER = 1; // set while holding write lock
//等待
static final int WAITER = 2; // set when waiting for write lock
//讀鎖
static final int READER = 4; // increment value for setting read lock

lockRoot

private final void lockRoot() {
    //嘗試加寫鎖
    if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
        //加寫鎖失敗則嘗試將線程掛起進(jìn)行等待
        contendedLock();
}

通過cas操作嘗試加寫鎖,當(dāng)加寫鎖失敗則會(huì)調(diào)用contendedLock方法將線程掛起進(jìn)行等待。

contendedLock

private final void contendedLock() {
    boolean waiting = false;
    for (int s; ; ) {
        /**
         * ~WAITER = 1111 1111 1111 1111 1111 1111 1111 1101
         * ((s = lockState) & ~WAITER) == 0 等于0則說明當(dāng)前沒有線程對(duì)當(dāng)前TreeBin對(duì)象下的紅黑樹進(jìn)行加鎖
         * 不等于則說明有線程對(duì)TreeBin對(duì)象下的紅黑樹加鎖
         */
        if (((s = lockState) & ~WAITER) == 0) {
            //沒有線程對(duì)TreeBin對(duì)象下的紅黑樹加鎖
            //當(dāng)前線程則可以對(duì)TreeBin對(duì)象下的紅黑樹嘗試加鎖
            if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) {
                if (waiting)
                    //將等待線程清空
                    waiter = null;
                return;
            }
        //(s & WAITER) == 0 執(zhí)行當(dāng)前判斷語句的時(shí)候則說明有線程對(duì)紅黑樹加了鎖
        //如果(s & WAITER)等于0則說明沒有線程在等待加鎖
        //此時(shí)當(dāng)前線程就可以進(jìn)行等待
        } else if ((s & WAITER) == 0) {
            //將鎖狀態(tài)的32位二進(jìn)制中的第2位設(shè)置為1
            //代表當(dāng)前有一個(gè)線程正在等待加鎖
            if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) {
                waiting = true;
                //將TreeBin中的等待線程設(shè)置為當(dāng)前線程
                waiter = Thread.currentThread();
            }
        } else if (waiting)
            //當(dāng)前線程已經(jīng)設(shè)置成了等待狀態(tài)了,此時(shí)就需要將線程掛起
            LockSupport.park(this);
    }
}

contendedLock方法中總共有3個(gè)分支,我們就一個(gè)一個(gè)的開始講解。

((s = lockState) & ~WAITER) == 0:校驗(yàn)是否有線程對(duì)指定索引位置上的TreeBin對(duì)象中的紅黑樹進(jìn)行加鎖。

~WAITER = -3,32位二進(jìn)制為 1111 1111 1111 1111 1111 1111 1111 1101

WRITER = 1,1為寫鎖,32位二進(jìn)制為 0000 0000 0000 0000 0000 0000 0000 0001

READER = 4,4為讀鎖,32位二進(jìn)制為 0000 0000 0000 0000 0000 0000 0000 0100

使用最新的鎖狀態(tài)與~WAITER進(jìn)行與運(yùn)算,當(dāng)加了讀鎖或?qū)戞i時(shí),讀鎖和寫鎖的二進(jìn)制標(biāo)識(shí)與~WAITER二進(jìn)制標(biāo)識(shí)都為1的情況下則說明已經(jīng)有線程加了鎖,如果有線程加了鎖,那當(dāng)前線程則會(huì)執(zhí)行后續(xù)的代碼來將自己掛起并等待。

(s & WAITER) == 0:執(zhí)行到當(dāng)前語句的時(shí)候就說明已經(jīng)有線程加了鎖,此時(shí)就需要校驗(yàn)一下是否有線程掛起并在等待,從代碼上來看,并不會(huì)出現(xiàn)多個(gè)線程同時(shí)等待,只會(huì)存在一個(gè)等待的線程,如果沒有線程在等待,則會(huì)通過cas操作將鎖狀態(tài)加上一個(gè)有線程在等待的標(biāo)識(shí),并將TreeBin對(duì)象中的等待線程設(shè)置為當(dāng)前線程。

waiting:當(dāng)有線程加了鎖,并且將當(dāng)前線程設(shè)置為了等待的線程,此時(shí)就需要將當(dāng)前線程掛起。

為什么說只會(huì)存在一個(gè)等待的線程呢?

讀鎖與寫鎖本就互斥,在get方法中,獲取紅黑樹中的節(jié)點(diǎn)的時(shí)候則會(huì)加上一個(gè)讀鎖,此時(shí)寫鎖就需要掛起進(jìn)行等待,當(dāng)讀鎖釋放完成之后就會(huì)喚醒加寫鎖的線程,如果已經(jīng)有線程加了寫鎖,那get方法則不會(huì)從紅黑樹中獲取節(jié)點(diǎn),而是從TreeBin對(duì)象中記錄的鏈表的頭節(jié)點(diǎn)開始遍歷進(jìn)行匹配,那會(huì)不會(huì)發(fā)生多個(gè)線程同時(shí)去加寫鎖呢?其實(shí)并不會(huì),因?yàn)榧訉戞i的方法的外部整個(gè)都被synchronized鎖住了,所以并不會(huì)存在多個(gè)線程同時(shí)加寫鎖,也不會(huì)存在多個(gè)等待的線程。

鏈表添加節(jié)點(diǎn)和紅黑樹添加節(jié)點(diǎn)都已經(jīng)講解完畢,此時(shí)就要看一下后續(xù)代碼,鏈表節(jié)點(diǎn)在什么情況下會(huì)轉(zhuǎn)換為紅黑樹以及是怎么轉(zhuǎn)換的。

if (binCount != 0) {
    //校驗(yàn)索引位置上的節(jié)點(diǎn)的數(shù)量是否到達(dá)樹化的閾值
    //如果該索引位置上的節(jié)點(diǎn)本來就是樹節(jié)點(diǎn)則不會(huì)繼續(xù)樹化
    //只有當(dāng)索引位置上的節(jié)點(diǎn)是鏈表節(jié)點(diǎn)并且節(jié)點(diǎn)的數(shù)量大于等于8的時(shí)候才會(huì)樹化
    if (binCount >= TREEIFY_THRESHOLD)
        //索引位置上的節(jié)點(diǎn)數(shù)量已經(jīng)到達(dá)樹化的閾值
        //則將該索引位置上的所有節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn)
        treeifyBin(tab, i);
    if (oldVal != null)
        //返回被替換的舊值
        return oldVal;
    break;
}

在添加一個(gè)節(jié)點(diǎn)到鏈表中去,會(huì)從鏈表的頭節(jié)點(diǎn)開始遍歷并記錄鏈表中的節(jié)點(diǎn)數(shù)量,該數(shù)量就是用binCount記錄的,當(dāng)鏈表中的節(jié)點(diǎn)數(shù)量大于等于TREEIFY_THRESHOLD(8)的時(shí)候則會(huì)調(diào)用treeifyBin方法來決定是否需要將鏈表中的所有節(jié)點(diǎn)樹化并轉(zhuǎn)換為紅黑樹。

treeifyBin

private final void treeifyBin(Node<K, V>[] tab, int index) {
    //指定索引位置上的節(jié)點(diǎn)
    Node<K, V> b;
    //n 數(shù)組長度
    int n, sc;
    if (tab != null) {
        //校驗(yàn)數(shù)組的長度是否小于最小的樹化閾值
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            /**
             * 數(shù)組長度小于最小的樹化閾值
             * 此時(shí)并不會(huì)將數(shù)組中的鏈表節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn)
             * 只會(huì)將數(shù)組進(jìn)行擴(kuò)容
             *
             * 如果仔細(xì)查看tryPresize方法你會(huì)發(fā)現(xiàn)該方法中也對(duì)n進(jìn)行了左移1位
             * 當(dāng)前tryPresize傳參的時(shí)候就已經(jīng)對(duì)n進(jìn)行了左移1位,然后在方法里面又對(duì)n進(jìn)行了左移1位
             * 這樣是不是就對(duì)數(shù)組進(jìn)行了4倍擴(kuò)容,并不是我們想的那樣2倍擴(kuò)容呢?
             * 其實(shí)不是的,在tryPresize方法里面并沒有使用2次位移后的n來擴(kuò)容
             * 而是使用最開始的數(shù)組長度n來計(jì)算進(jìn)行擴(kuò)容
             */
            tryPresize(n << 1);
            //獲取指定索引位置上的元素節(jié)點(diǎn)并校驗(yàn)元素節(jié)點(diǎn)是否不為空
            //如果元素節(jié)點(diǎn)不為空則校驗(yàn)該節(jié)點(diǎn)的hash值是大于等于0
            //如果hash值大于等于0則說明該節(jié)點(diǎn)需要轉(zhuǎn)換為樹節(jié)點(diǎn)
            //如果hash值小于0則說明該節(jié)點(diǎn)正在移動(dòng)或已經(jīng)被樹化了
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            //使用鎖鎖住指定索引位置上的節(jié)點(diǎn)
            //在添加元素的時(shí)候也會(huì)對(duì)指定索引位置上的節(jié)點(diǎn)加鎖
            //如果兩個(gè)索引位置都是相同的索引位置的時(shí)候,如果添加元素的線程先加了鎖
            //那執(zhí)行到當(dāng)前方法的線程則會(huì)等待添加元素的線程釋放鎖
            //當(dāng)添加元素的線程釋放了鎖之后,當(dāng)前方法的線程則會(huì)獲取鎖將索引位置上所有的節(jié)點(diǎn)都樹化,包括新添加的元素節(jié)點(diǎn)
            //如果是執(zhí)行到當(dāng)前方法的線程獲取到了鎖,那添加元素的線程則會(huì)等待
            //當(dāng)指定索引位置上的所有節(jié)點(diǎn)都樹化了之后并釋放了鎖
            //添加元素的線程則會(huì)去獲取鎖,此時(shí)添加元素的線程會(huì)發(fā)現(xiàn)當(dāng)前索引位置上的節(jié)點(diǎn)已經(jīng)是樹節(jié)點(diǎn)
            //則會(huì)將元素節(jié)點(diǎn)添加到紅黑樹中并使紅黑樹平衡
            synchronized (b) {
                //校驗(yàn)在獲取鎖的時(shí)候指定索引位置上的節(jié)點(diǎn)是否被更改
                if (tabAt(tab, index) == b) {
                    //hd 頭節(jié)點(diǎn)
                    //tl 當(dāng)前遍歷到的節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)
                    TreeNode<K, V> hd = null, tl = null;
                    //從指定索引位置上的節(jié)點(diǎn)開始遍歷,依次將鏈表上的所有節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn)
                    for (Node<K, V> e = b; e != null; e = e.next) {
                        //將普通節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn)
                        TreeNode<K, V> p = new TreeNode<K, V>(e.hash, e.key, e.val, null, null);
                        //校驗(yàn)當(dāng)前遍歷的節(jié)點(diǎn)是否是鏈表中的頭節(jié)點(diǎn)
                        //如果是頭節(jié)點(diǎn)則將該節(jié)點(diǎn)設(shè)置為了樹節(jié)點(diǎn)的頭節(jié)點(diǎn)
                        //如果不是頭節(jié)點(diǎn)則將當(dāng)前轉(zhuǎn)換的樹節(jié)點(diǎn)與上一個(gè)樹節(jié)點(diǎn)進(jìn)行關(guān)聯(lián)
                        if ((p.prev = tl) == null)
                            hd = p;
                        else
                            tl.next = p;
                        tl = p;
                    }
                    //創(chuàng)建一個(gè)TreeBin對(duì)象并將樹化的節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹
                    //TreeBin對(duì)象中保留著紅黑樹的根節(jié)點(diǎn)以及鏈表的頭節(jié)點(diǎn)
                    //再調(diào)用setTabAt方法將TreeBin對(duì)象添加到指定的索引位置上
                    //在HashMap中并沒有創(chuàng)建一個(gè)TreeBin對(duì)象來存放紅黑樹的根節(jié)點(diǎn)
                    //而是直接將紅黑樹的根節(jié)點(diǎn)放置到指定的索引位置上
                    setTabAt(tab, index, new TreeBin<K, V>(hd));
                }
            }
        }
    }
}

(n = tab.length) < MIN_TREEIFY_CAPACITY:校驗(yàn)數(shù)組的長度是否小于最小樹化的閾值,并不是說鏈表中的節(jié)點(diǎn)數(shù)量到達(dá)8了就要將鏈表節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹節(jié)點(diǎn),前提是需要數(shù)組的長度到達(dá)了閾值(64)才會(huì)轉(zhuǎn)換為紅黑樹節(jié)點(diǎn),如果數(shù)組的長度沒有到達(dá)閾值則會(huì)進(jìn)行擴(kuò)容。

(b = tabAt(tab, index)) != null && b.hash >= 0:校驗(yàn)外部synchronized釋放鎖之后,準(zhǔn)備節(jié)點(diǎn)樹化的期間是否有其它線程對(duì)該節(jié)點(diǎn)進(jìn)行操作了,該操作分為兩種,有線程將該索引位置上的所有節(jié)點(diǎn)都刪除了或者說有線程將該索引位置上的節(jié)點(diǎn)都樹化了,這兩種情況下就不需要再進(jìn)行樹化了。 我們主要看一下是怎么樹化的以及轉(zhuǎn)換成紅黑樹的,樹化的操作其實(shí)很簡單,就是對(duì)指定索引位置上的節(jié)點(diǎn)加鎖防止其它線程操作,依次從鏈表的頭節(jié)點(diǎn)開始遍歷,將鏈表中的Node節(jié)點(diǎn)轉(zhuǎn)換為TreeNode節(jié)點(diǎn),就這樣樹化就完成了,而轉(zhuǎn)換為紅黑樹就是通過創(chuàng)建一個(gè)TreeBin對(duì)象來完成的,在構(gòu)造TreeBin對(duì)象時(shí)會(huì)將樹化的節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹中的節(jié)點(diǎn)。

TreeBin

TreeBin(TreeNode<K, V> b) {
    //給當(dāng)前TreeBin對(duì)象設(shè)置一個(gè)樹化標(biāo)識(shí)
    super(TREEBIN, null, null, null);
    //樹化的頭節(jié)點(diǎn)的設(shè)置為鏈表的頭節(jié)點(diǎn)
    this.first = b;
    //根節(jié)點(diǎn)
    TreeNode<K, V> r = null;
    //從樹化的頭節(jié)點(diǎn)開始遍歷將節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹中的節(jié)點(diǎn),直到?jīng)]有下一個(gè)節(jié)點(diǎn)
    for (TreeNode<K, V> x = b, next; x != null; x = next) {
        //下一個(gè)節(jié)點(diǎn)
        next = (TreeNode<K, V>) x.next;
        //初始化每個(gè)樹節(jié)點(diǎn)的左右子節(jié)點(diǎn)
        x.left = x.right = null;
        if (r == null) {
            //根節(jié)點(diǎn)為空則將當(dāng)前遍歷到的節(jié)點(diǎn)設(shè)置為根節(jié)點(diǎn)
            //并將該節(jié)點(diǎn)的顏色設(shè)置為黑色
            x.parent = null;
            x.red = false;
            r = x;
        } else {
            //待添加到紅黑樹中的節(jié)點(diǎn)的key
            K k = x.key;
            //待添加到紅黑樹中的節(jié)點(diǎn)的hash值
            int h = x.hash;
            Class<?> kc = null;
            //從紅黑樹的根節(jié)點(diǎn)開始遍歷來校驗(yàn)節(jié)點(diǎn)放置的位置
            for (TreeNode<K, V> p = r; ; ) {
                //dir 節(jié)點(diǎn)添加到紅黑樹中的位置
                //ph 被遍歷到的紅黑樹中的節(jié)點(diǎn)的key
                int dir, ph;
                //被遍歷到的紅黑樹中的節(jié)點(diǎn)的key
                K pk = p.key;
                if ((ph = p.hash) > h)
                    //被遍歷到的紅黑樹中的節(jié)點(diǎn)的hash值大于待添加到紅黑樹中的節(jié)點(diǎn)的hash值
                    //則需要將節(jié)點(diǎn)添加到紅黑樹中的左子節(jié)點(diǎn)
                    dir = -1;
                else if (ph < h)
                    //被遍歷到的紅黑樹中的節(jié)點(diǎn)的hash值小于待添加到紅黑樹中的節(jié)點(diǎn)的hash值
                    //則需要將節(jié)點(diǎn)添加到紅黑樹中的右子節(jié)點(diǎn)
                    dir = 1;
                else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0)
                    dir = tieBreakOrder(k, pk);
                TreeNode<K, V> xp = p;
                //校驗(yàn)待添加到紅黑樹中的節(jié)點(diǎn)所放置的位置是否有節(jié)點(diǎn)存在
                //如果有節(jié)點(diǎn)存在則重新走循環(huán)與子節(jié)點(diǎn)繼續(xù)比較直到?jīng)]有了子節(jié)點(diǎn)
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    //放置待添加到紅黑樹中的節(jié)點(diǎn)的位置沒有了節(jié)點(diǎn)
                    //則會(huì)將節(jié)點(diǎn)添加到紅黑樹中
                    //將待添加節(jié)點(diǎn)的父節(jié)點(diǎn)的指針指向當(dāng)前遍歷到的紅黑樹中的節(jié)點(diǎn)
                    x.parent = xp;
                    if (dir <= 0)
                        //將待添加的節(jié)點(diǎn)放置到被遍歷到的節(jié)點(diǎn)的左子節(jié)點(diǎn)
                        xp.left = x;
                    else
                        //將待添加的節(jié)點(diǎn)放置到被遍歷到的節(jié)點(diǎn)的右子節(jié)點(diǎn)
                        xp.right = x;
                    //紅黑樹中添加了新的節(jié)點(diǎn),可能讓紅黑樹不在平衡
                    //所以需要將紅黑樹中的節(jié)點(diǎn)進(jìn)行平衡
                    r = balanceInsertion(r, x);
                    break;
                }
            }
        }
    }
    //TreeBin對(duì)象記錄根節(jié)點(diǎn)
    this.root = r;
    assert checkInvariants(root);
}

TreeBin的構(gòu)造方法比較簡單,通過super方法給當(dāng)前的TreeBin對(duì)象設(shè)置hash值為-2,然后通過遍歷樹化后的鏈表節(jié)點(diǎn),將鏈表的頭節(jié)點(diǎn)設(shè)置為紅黑樹的根節(jié)點(diǎn),后續(xù)鏈表中的節(jié)點(diǎn)從紅黑樹的根節(jié)點(diǎn)開始進(jìn)行比較,來獲取到鏈表的節(jié)點(diǎn)在紅黑樹中所存放的位置,紅黑樹按左小右大的方式存放節(jié)點(diǎn),每添加一個(gè)節(jié)點(diǎn)到紅黑樹中,都有可能讓紅黑樹不平衡,所以每次都會(huì)嘗試是否需要平衡紅黑樹中的節(jié)點(diǎn)。

上圖是鏈表轉(zhuǎn)紅黑樹,轉(zhuǎn)為紅黑樹之后,索引位置上是一個(gè)TreeBin對(duì)象,并且TreeBin對(duì)象中包含了紅黑樹的根節(jié)點(diǎn)以及鏈表的頭節(jié)點(diǎn),而紅黑樹中的節(jié)點(diǎn)中不僅有紅黑樹本身的指針,而且還有鏈表的一些指針,既能當(dāng)做是紅黑樹也可以當(dāng)做鏈表,上圖中并沒有將所有節(jié)點(diǎn)的鏈表指針都畫出來,只是畫了部分節(jié)點(diǎn)的鏈表指針。

當(dāng)元素添加操作以及鏈表轉(zhuǎn)換為紅黑樹操作完成之后,我們再來看ConcurrentHashMap是如何記錄元素?cái)?shù)量的以及擴(kuò)容的。

addCount

/**
 * 添加元素節(jié)點(diǎn)或刪除元素節(jié)點(diǎn)的時(shí)候需要將計(jì)數(shù)器中的值修改
 * 如果在單線程的情況下直接對(duì)基本計(jì)數(shù)器值修改
 * 如果在多線程的情況下對(duì)基本計(jì)數(shù)器修改失敗的話并且計(jì)數(shù)器數(shù)組為空
 * 則需要初始化計(jì)數(shù)器數(shù)組,并使用線程生成隨機(jī)數(shù)與計(jì)數(shù)器數(shù)組的長度進(jìn)行運(yùn)算獲取到指定的索引位置
 * 并將指定索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行初始化并將check值保存在計(jì)數(shù)器對(duì)象中的value
 * 如果計(jì)數(shù)器數(shù)組不為空,但是指定的索引位置上的計(jì)數(shù)器對(duì)象為空
 * 則初始化計(jì)數(shù)器對(duì)象并將check值保存在計(jì)數(shù)器對(duì)象中的value
 * 如果計(jì)數(shù)器對(duì)象也不為空則直接將check值累加到計(jì)數(shù)器對(duì)象中的value
 * 在將check值保存之后則會(huì)校驗(yàn)當(dāng)前集合中的數(shù)組是否需要擴(kuò)容
 * 如果需要擴(kuò)容則會(huì)調(diào)用transfer方法來進(jìn)行擴(kuò)容
 */
private final void addCount(long x, int check) {
    //計(jì)數(shù)器數(shù)組
    CounterCell[] as;
    //b 基本計(jì)數(shù)器值
    long b, s;
    //計(jì)數(shù)器數(shù)組counterCells不為空則說明之前已經(jīng)發(fā)生過多線程對(duì)基本計(jì)數(shù)器baseCount進(jìn)行操作
    //計(jì)數(shù)器數(shù)組counterCells為空則說明之前一直在單線程的情況下對(duì)基本計(jì)數(shù)器baseCount進(jìn)行操作
    //如果計(jì)數(shù)器數(shù)組為空則會(huì)嘗試對(duì)基本計(jì)數(shù)器baseCount進(jìn)行操作,如果對(duì)baseCount操作失敗則說明當(dāng)前處于多線程的情況下
    //此時(shí)就需要初始化counterCells
    if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a;
        long v;
        int m;
        boolean uncontended = true;
        //as == null || (m = as.length - 1) < 0 校驗(yàn)計(jì)數(shù)器數(shù)組是否初始化,如果沒有初始化則調(diào)用fullAddCount方法進(jìn)行初始化
        //(a = as[ThreadLocalRandom.getProbe() & m]) == null 當(dāng)前計(jì)數(shù)器數(shù)組不為空的時(shí)候則使用線程生成的隨機(jī)數(shù)
        //與計(jì)數(shù)器數(shù)組的長度進(jìn)行與運(yùn)算獲取到指定索引位置上的計(jì)數(shù)器對(duì)象,并校驗(yàn)計(jì)數(shù)器對(duì)象是否為空
        //如果計(jì)數(shù)器對(duì)象為空則調(diào)用fullAddCount方法對(duì)指定索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行初始化
        //!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) 計(jì)數(shù)器數(shù)組不為空并且運(yùn)算獲取到的指定索引位置上的計(jì)數(shù)器對(duì)象也不為空
        //此時(shí)就可以對(duì)計(jì)數(shù)器對(duì)象進(jìn)行操作,如果操作失敗則說明有其它線程獲取到了這個(gè)索引位置上的計(jì)數(shù)器對(duì)象并對(duì)這個(gè)計(jì)數(shù)器進(jìn)行操作中
        //此時(shí)就需要調(diào)用fullAddCount方法選擇其它索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行操作
        if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            //對(duì)計(jì)數(shù)器數(shù)組以及計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行初始化
            //一個(gè)線程對(duì)計(jì)數(shù)器對(duì)象操作失敗則會(huì)更換其它索引上的計(jì)數(shù)器對(duì)象進(jìn)行操作
            //如果這個(gè)線程多次對(duì)計(jì)數(shù)器對(duì)象操作失敗并且當(dāng)前計(jì)數(shù)器數(shù)組的長度小于cpu的數(shù)量
            //此時(shí)就會(huì)嘗試進(jìn)行擴(kuò)容,然后對(duì)擴(kuò)容之后的數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作
            //如果說計(jì)數(shù)器數(shù)組的長度不小于cpu的數(shù)量則不會(huì)進(jìn)行擴(kuò)容
            //只會(huì)一直更換計(jì)數(shù)器對(duì)象進(jìn)行操作,直到操作成功
            fullAddCount(x, uncontended);
            return;
        }
        //校驗(yàn)check是否小于等于1
        //check=-1的情況下則說明刪除了指定索引位置上的元素節(jié)點(diǎn)
        //check=0的情況下則說明指定索引位置上沒有元素節(jié)點(diǎn),直接將指定的key和value封裝成一個(gè)節(jié)點(diǎn)放置到指定的索引位置上
        //check=1的情況在put方法下則說明指定索引位置上有元素節(jié)點(diǎn),但是只是對(duì)索引位置上的元素節(jié)點(diǎn)中的value進(jìn)行了替換
        //在其它方法中check=1可能表示在指定的索引位置上添加了1個(gè)元素節(jié)點(diǎn)
        //如果是這三種情況的話則不會(huì)去嘗試進(jìn)行擴(kuò)容
        //如果執(zhí)行到了當(dāng)前判斷語句的話則說明并沒有執(zhí)行上面的fullAddCount方法
        //而是通過上面判斷語句中的cas操作對(duì)指定索引位置上的計(jì)數(shù)器對(duì)象的值操作成功
        //但是在check=1的情況下的put方法只是將原本的元素節(jié)點(diǎn)中的value替換了
        //如果此時(shí)還要對(duì)計(jì)數(shù)器對(duì)象中的value加1,那不就會(huì)造成1個(gè)元素節(jié)點(diǎn)變成了2個(gè)元素節(jié)點(diǎn)?
        //其實(shí)并不會(huì)的,如果check=1的情況下put方法并不會(huì)走到當(dāng)前的方法中
        if (check <= 1)
            return;
        //將計(jì)數(shù)器數(shù)組中所有的計(jì)數(shù)器對(duì)象值以及基本的計(jì)數(shù)器值合并獲取到當(dāng)前集合中的元素節(jié)點(diǎn)的數(shù)量
        s = sumCount();
    }
    //校驗(yàn)check是否大于等于0
    //大于等于0則說明添加了新的元素節(jié)點(diǎn)
    //此時(shí)就需要來校驗(yàn)是否需要進(jìn)行擴(kuò)容
    //check一共分為5種情況,有三種已經(jīng)在上面說過,現(xiàn)在來看一下剩下的兩種情況
    //check=2的情況下代表在紅黑樹種添加了一個(gè)樹節(jié)點(diǎn),也有可能是在一個(gè)鏈表節(jié)點(diǎn)中添加了一個(gè)節(jié)點(diǎn)
    //check>2的情況下代表在鏈表節(jié)點(diǎn)中添加了一個(gè)節(jié)點(diǎn)
    if (check >= 0) {
        Node<K, V>[] tab, nt;
        int n, sc;
        //當(dāng)前數(shù)組中的元素節(jié)點(diǎn)的數(shù)量已經(jīng)到達(dá)擴(kuò)容的閾值并且數(shù)組的長度小于數(shù)組最大容量大小
        //此時(shí)會(huì)嘗試將數(shù)組進(jìn)行擴(kuò)容,如果擴(kuò)容失敗則會(huì)一直進(jìn)行嘗試,直到擴(kuò)容成功
        while (s >= (long) (sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) {
            //獲取擴(kuò)容時(shí)的標(biāo)記
            int rs = resizeStamp(n);
            if (sc < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0)
                    break;
                //擴(kuò)容線程加1
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    //協(xié)助擴(kuò)容
                    transfer(tab, nt);
            //將sizeCtl修改成一個(gè)很大的負(fù)數(shù),告知其它線程現(xiàn)在有線程正在擴(kuò)容
            //為什么要+2而不是+1?+1代表當(dāng)前擴(kuò)容的線程數(shù)量+1,而+2可能是標(biāo)識(shí)開啟擴(kuò)容
            } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2))
                //擴(kuò)容
                transfer(tab, null);
            //重新計(jì)算集合中元素節(jié)點(diǎn)的數(shù)量
            s = sumCount();
        }
    }
}

addCount中分為兩個(gè)分支,分別為元素?cái)?shù)量計(jì)數(shù)以及數(shù)組擴(kuò)容,我們先看計(jì)數(shù)的分支。

在單線程的情況下只會(huì)對(duì)基本計(jì)數(shù)器baseCount進(jìn)行操作,如果在多線程的情況下同時(shí)對(duì)baseCount進(jìn)行操作,只會(huì)有一個(gè)線程操作成功,而其它線程并不會(huì)等待繼續(xù)對(duì)baseCount進(jìn)行操作,而是調(diào)用fullAddCount方法初始化計(jì)數(shù)器數(shù)組(CounterCells)以及數(shù)組中的計(jì)數(shù)器對(duì)象(CounterCell)并對(duì)數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作,只要出現(xiàn)過一次多線程的情況,往后都不會(huì)對(duì)baseCount操作了,而是直接對(duì)計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作,fullAddCount中代碼具體的意思可以參考下面代碼中的注釋,這里不再多講。

fullAddCount

private final void fullAddCount(long x, boolean wasUncontended) {
    int h;
    //使用當(dāng)前線程生成隨機(jī)數(shù),如果隨機(jī)數(shù)為0則說明ThreadLocalRandom中的一些參數(shù)還未被初始化
    //此時(shí)就需要進(jìn)行初始化并重新獲取隨機(jī)數(shù)
    if ((h = ThreadLocalRandom.getProbe()) == 0) {
        ThreadLocalRandom.localInit();
        h = ThreadLocalRandom.getProbe();
        wasUncontended = true;
    }
    boolean collide = false;
    for (; ; ) {
        //計(jì)數(shù)器數(shù)組
        CounterCell[] as;
        //計(jì)數(shù)器對(duì)象
        CounterCell a;
        //計(jì)數(shù)器數(shù)組長度
        int n;
        //計(jì)數(shù)器對(duì)象中的value
        long v;
        //校驗(yàn)計(jì)數(shù)器數(shù)組是否已初始化
        if ((as = counterCells) != null && (n = as.length) > 0) {
            //校驗(yàn)當(dāng)前線程生成的隨機(jī)數(shù)與計(jì)數(shù)器數(shù)組索引長度運(yùn)算后獲取到的指定索引位置上的計(jì)數(shù)器對(duì)象是否為空
            if ((a = as[(n - 1) & h]) == null) {
                //校驗(yàn)計(jì)數(shù)器數(shù)組的鎖狀態(tài)是否為0
                //如果不為0則說明有其它線程正在對(duì)計(jì)數(shù)器數(shù)組進(jìn)行擴(kuò)容或?qū)τ?jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行初始化
                if (cellsBusy == 0) {
                    //創(chuàng)建一個(gè)計(jì)數(shù)器對(duì)象,并將值添加到計(jì)數(shù)器對(duì)象中
                    CounterCell r = new CounterCell(x);
                    //加鎖,防止多個(gè)線程同時(shí)對(duì)通一個(gè)索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行初始化
                    if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                        boolean created = false;
                        try {
                            CounterCell[] rs;
                            int m, j;
                            //校驗(yàn)一下運(yùn)算后的索引位置上的計(jì)數(shù)器對(duì)象是否為空
                            //如果不為空則說明已經(jīng)被其它線程初始化了,當(dāng)前線程就不需要再去初始化
                            if ((rs = counterCells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {
                                //將創(chuàng)建好的計(jì)數(shù)器對(duì)象添加到運(yùn)算后的指定索引位置上
                                rs[j] = r;
                                //初始化成功
                                created = true;
                            }
                        } finally {
                            //修改計(jì)數(shù)器數(shù)組鎖標(biāo)識(shí)
                            cellsBusy = 0;
                        }
                        if (created)
                            //初始化成功則退出
                            break;
                        //初始化失敗,說明已經(jīng)有其它線程初始化了計(jì)數(shù)器對(duì)象,此時(shí)就需要重新走循環(huán)選擇初始化成功的計(jì)數(shù)器對(duì)象進(jìn)行操作
                        continue;
                    }
                }
                //其它線程正在對(duì)計(jì)數(shù)器數(shù)組進(jìn)行擴(kuò)容或初始化指定索引位置上的計(jì)數(shù)器對(duì)象
                collide = false;
            } else if (!wasUncontended)
                //wasUncontended為false則說明有其它線程正在對(duì)指定索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行操作
                //此時(shí)當(dāng)前線程則需要獲取其它索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行操作
                wasUncontended = true;
            //嘗試對(duì)指定索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行操作
            else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                //執(zhí)行成功則退出
                break;
            //校驗(yàn)當(dāng)前是否有線程對(duì)計(jì)數(shù)器數(shù)組進(jìn)行了擴(kuò)容,將計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象移動(dòng)到了新的計(jì)數(shù)器數(shù)組中
            //如果已經(jīng)將計(jì)數(shù)器對(duì)象移動(dòng)到了新的計(jì)數(shù)器數(shù)組中,那重新選擇新的計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象來進(jìn)行操作
            //如果沒有線程對(duì)計(jì)數(shù)器數(shù)組進(jìn)行擴(kuò)容,那比較一下當(dāng)前計(jì)數(shù)器數(shù)組的長度是否大于等于cpu的數(shù)量
            //如果大于等于cpu的數(shù)量則不會(huì)進(jìn)行擴(kuò)容,只會(huì)重新選擇計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象來操作
            //如果計(jì)數(shù)器數(shù)組的長度小于cpu的數(shù)量那就會(huì)對(duì)計(jì)數(shù)器數(shù)組進(jìn)行2倍的擴(kuò)容
            else if (counterCells != as || n >= NCPU)
                //此處將collide修改為false可能是想再次嘗試一下是否能對(duì)計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作
                //如果能操作則不進(jìn)行擴(kuò)容,這樣能減少擴(kuò)容帶來的性能問題
                collide = false;
            else if (!collide)
                collide = true;
            //多次獲取計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象而不能操作,并計(jì)數(shù)器數(shù)組的長度小于cpu的數(shù)量
            //導(dǎo)致部分的cpu沒有被使用到,此時(shí)就需要對(duì)計(jì)數(shù)器數(shù)組進(jìn)行擴(kuò)容,充分的使用cpu
            //對(duì)計(jì)數(shù)器數(shù)組加鎖
            else if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                try {
                    //校驗(yàn)計(jì)數(shù)器數(shù)組是否變更,防止其它線程已經(jīng)對(duì)計(jì)數(shù)器數(shù)組進(jìn)行了擴(kuò)容
                    if (counterCells == as) {
                        //初始化一個(gè)新的計(jì)數(shù)器數(shù)組,大小是原來的2倍
                        CounterCell[] rs = new CounterCell[n << 1];
                        //循環(huán)將原來的計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象移動(dòng)到擴(kuò)容后的計(jì)數(shù)器數(shù)組中
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        //更新當(dāng)前集合中的計(jì)數(shù)器數(shù)組
                        counterCells = rs;
                    }
                } finally {
                    //修改計(jì)數(shù)器數(shù)組鎖標(biāo)識(shí)
                    cellsBusy = 0;
                }
                collide = false;
                //使用擴(kuò)容后的計(jì)數(shù)器數(shù)組進(jìn)行操作
                continue;
            }
            //計(jì)數(shù)器對(duì)象正在被其它線程操作,此時(shí)需要重新生成隨機(jī)數(shù),獲取別的索引位置上的計(jì)數(shù)器對(duì)象來進(jìn)行操作
            h = ThreadLocalRandom.advanceProbe(h);
        //計(jì)數(shù)器數(shù)組未被初始化,此時(shí)就需要校驗(yàn)一下當(dāng)前是否有其它線程正在初始化計(jì)數(shù)器數(shù)組
        //如果沒有,那當(dāng)前線程就需要將計(jì)數(shù)器數(shù)組初始化的標(biāo)識(shí)設(shè)置為1,代表當(dāng)前有線程正在初始化計(jì)數(shù)器數(shù)組
        } else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
            boolean init = false;
            try {
                if (counterCells == as) {
                    //創(chuàng)建計(jì)數(shù)器數(shù)組,計(jì)數(shù)器數(shù)組的長度為2的次方
                    CounterCell[] rs = new CounterCell[2];
                    //創(chuàng)建一個(gè)計(jì)數(shù)器對(duì)象,并將值添加到計(jì)數(shù)器對(duì)象中
                    //并使用線程生成的隨機(jī)數(shù)與計(jì)數(shù)器數(shù)組索引長度進(jìn)行運(yùn)算獲取到指定的索引位置
                    //并將計(jì)數(shù)器對(duì)象放置到指定的索引位置上
                    rs[h & 1] = new CounterCell(x);
                    //更新當(dāng)前集合中的計(jì)數(shù)器數(shù)組
                    counterCells = rs;
                    //初始化完成
                    init = true;
                }
            } finally {
                //修改計(jì)數(shù)器數(shù)組鎖標(biāo)識(shí)
                cellsBusy = 0;
            }
            if (init)
                //計(jì)數(shù)器數(shù)組初始化完成則退出
                break;
        //計(jì)數(shù)器數(shù)組正在被其它線程初始化或已經(jīng)被其它線程初始化完成
        //此時(shí)嘗試對(duì)基本的計(jì)數(shù)器值進(jìn)行操作
        //如果失敗則說明有其它線程也在對(duì)基本計(jì)數(shù)器值進(jìn)行操作
        //那就要重新走循環(huán),來看計(jì)數(shù)器數(shù)組是否被初始化完成,如果被初始化完成那就對(duì)計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作
        //如果還沒有初始化完成則會(huì)繼續(xù)嘗試對(duì)基本的計(jì)數(shù)器值進(jìn)行操作
        } else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
            break;
    }
}

我們再看addCount中的第二個(gè)分支,該分支中主要是先校驗(yàn)是否到達(dá)了擴(kuò)容的閾值以及是否小于數(shù)組最大的容量大小,條件成立則會(huì)校驗(yàn)是否有線程在擴(kuò)容,如果有線程在擴(kuò)容,那當(dāng)前線程則需要協(xié)助擴(kuò)容如果沒有那當(dāng)前線程則開啟擴(kuò)容。

transfer

private final void transfer(Node<K, V>[] tab, Node<K, V>[] nextTab) {
    //n 數(shù)組長度
    //stride 每個(gè)cpu需要負(fù)責(zé)轉(zhuǎn)移的索引長度
    int n = tab.length, stride;
    //使用數(shù)組長度來計(jì)算每個(gè)cpu需要負(fù)責(zé)的長度
    //每個(gè)cpu最少需要負(fù)責(zé)16的長度
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
        stride = MIN_TRANSFER_STRIDE;
    //nextTab為空則說明當(dāng)前沒有其它線程在初始化
    //不為空則說明當(dāng)前有其它線程正在初始化,當(dāng)前線程則需要幫助初始化的線程將舊數(shù)組中的元素節(jié)點(diǎn)轉(zhuǎn)移到新的數(shù)組中
    if (nextTab == null) {
        try {
            //創(chuàng)建新的數(shù)組,容量是舊數(shù)組的2倍
            @SuppressWarnings("unchecked")
            Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {
            //出現(xiàn)異常時(shí)則說明擴(kuò)容失敗,容量大小已經(jīng)超出MAXIMUM_CAPACITY參數(shù)指定的最大的數(shù)組容量
            //因?yàn)镸AXIMUM_CAPACITY參數(shù)指定的容量大小是int類型中正整數(shù)最大的2的次方
            //當(dāng)數(shù)組的長度已經(jīng)到達(dá)MAXIMUM_CAPACITY參數(shù)指定的容量大小時(shí),如果再進(jìn)行2倍的擴(kuò)容就會(huì)導(dǎo)致數(shù)組的長度變成負(fù)數(shù)
            //此時(shí)就會(huì)導(dǎo)致擴(kuò)容失敗,將int類型中最大的正整數(shù)設(shè)置成擴(kuò)容的閾值來停止本次擴(kuò)容
            //如果數(shù)組的長度已到達(dá)數(shù)組最大的容量大小那就不會(huì)進(jìn)行擴(kuò)容了
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        //擴(kuò)容后的數(shù)組,當(dāng)其它擴(kuò)容的線程發(fā)現(xiàn)nextTable不為空時(shí)則不會(huì)重復(fù)擴(kuò)容
        nextTable = nextTab;
        //擴(kuò)容后,默認(rèn)需要轉(zhuǎn)移整個(gè)舊數(shù)組
        transferIndex = n;
    }
    //獲取擴(kuò)容后的數(shù)組長度
    int nextn = nextTab.length;
    //創(chuàng)建一個(gè)轉(zhuǎn)移的節(jié)點(diǎn)
    ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab);
    //是否繼續(xù)轉(zhuǎn)移
    boolean advance = true;
    //標(biāo)識(shí)擴(kuò)容之后舊數(shù)組中的節(jié)點(diǎn)是否全部完成轉(zhuǎn)移
    boolean finishing = false;
    for (int i = 0, bound = 0; ; ) {
        Node<K, V> f;
        int fh;
        while (advance) {
            //nextIndex 當(dāng)前線程開始轉(zhuǎn)移的長度位置
            //nextBound 當(dāng)前線程負(fù)責(zé)轉(zhuǎn)移的索引位置邊界,如果到達(dá)這個(gè)邊界則說明當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引長度的節(jié)點(diǎn)
            //轉(zhuǎn)移舊數(shù)組中的元素節(jié)點(diǎn)是從數(shù)組的尾部向數(shù)組的頭部開始轉(zhuǎn)移
            int nextIndex, nextBound;
            //每次轉(zhuǎn)移完一個(gè)索引位置上的節(jié)點(diǎn)的時(shí)候都會(huì)校驗(yàn)一下下一次轉(zhuǎn)移的索引位置是否已經(jīng)超出邊界
            //或舊數(shù)組中的元素節(jié)點(diǎn)都已經(jīng)全部轉(zhuǎn)移完成
            //如果下一次轉(zhuǎn)移的索引位置超出邊界,但是剩下需要轉(zhuǎn)移的節(jié)點(diǎn)沒有被其它線程協(xié)助轉(zhuǎn)移
            //那么當(dāng)前線程則繼續(xù)選擇部分的索引長度來轉(zhuǎn)移
            if (--i >= bound || finishing)
                advance = false;
            //校驗(yàn)剩余需要轉(zhuǎn)移的索引長度是否為0,如果為0則說明舊數(shù)組中沒有需要轉(zhuǎn)移的元素節(jié)點(diǎn)了
            else if ((nextIndex = transferIndex) <= 0) {
                //將轉(zhuǎn)移的節(jié)點(diǎn)的索引位置設(shè)置為-1,后續(xù)會(huì)根據(jù)該條件退出擴(kuò)容方法
                i = -1;
                advance = false;
            //根據(jù)開始轉(zhuǎn)移的長度位置與每個(gè)線程需要轉(zhuǎn)移的長度進(jìn)行比較
            //如果開始轉(zhuǎn)移的長度位置大于每個(gè)線程需要轉(zhuǎn)移的長度,那就使用開始轉(zhuǎn)移的長度位置減去每個(gè)線程需要轉(zhuǎn)移的長度
            //獲取到當(dāng)前線程需要負(fù)責(zé)轉(zhuǎn)移的索引位置邊界
            //如果開始轉(zhuǎn)移的長度位置小于等于每個(gè)線程需要轉(zhuǎn)移的長度,那就說明當(dāng)前需要轉(zhuǎn)移的長度不需要其它線程協(xié)助
            //當(dāng)前線程則會(huì)將剩下需要轉(zhuǎn)移的節(jié)點(diǎn)轉(zhuǎn)移到新的數(shù)組中
            } else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
                //將計(jì)算后的索引邊界賦予bound,用于線程每次轉(zhuǎn)移之后進(jìn)行校驗(yàn)
                //如果已經(jīng)到達(dá)了邊界說明線程已經(jīng)完成了所負(fù)責(zé)的索引長度的節(jié)點(diǎn)轉(zhuǎn)移
                //線程不再執(zhí)行節(jié)點(diǎn)的轉(zhuǎn)移
                bound = nextBound;
                //開始轉(zhuǎn)移的長度位置-1,獲取到開始轉(zhuǎn)移的節(jié)點(diǎn)的索引位置
                i = nextIndex - 1;
                advance = false;
            }
        }
        //i < 0 小于0則說明當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的節(jié)點(diǎn)已經(jīng)完成并且沒有其它需要轉(zhuǎn)移的節(jié)點(diǎn)了
        //i >= n
        //i + n >= nextn
        if (i < 0 || i >= n || i + n >= nextn) {
            int sc;
            //當(dāng)前finishing為true時(shí)則說明舊數(shù)組中的所有節(jié)點(diǎn)都已經(jīng)轉(zhuǎn)移
            //此時(shí)就需要將新數(shù)組設(shè)置為當(dāng)前集合使用的數(shù)組并計(jì)算下一次的擴(kuò)容閾值
            if (finishing) {
                //將擴(kuò)容的數(shù)組置為空,代表當(dāng)前沒有線程正在進(jìn)行擴(kuò)容操作
                nextTable = null;
                //將擴(kuò)容后的新數(shù)組設(shè)置為當(dāng)前集合正在使用的數(shù)組
                table = nextTab;
                //計(jì)算下一次擴(kuò)容的閾值
                sizeCtl = (n << 1) - (n >>> 1);
                //擴(kuò)容完成,退出
                return;
            }
            //finishing為false則會(huì)走到當(dāng)前語句,則說明當(dāng)前線程并不知道舊數(shù)組中的元素節(jié)點(diǎn)有沒有轉(zhuǎn)移完成
            //但是當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的索引節(jié)點(diǎn)已經(jīng)完成,此時(shí)就需要將并發(fā)擴(kuò)容中的線程數(shù)量減1
            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                //擴(kuò)容標(biāo)記左移16位獲取到線程數(shù)量的標(biāo)記
                //使用sc-2獲取到當(dāng)前還剩下的線程數(shù)量的標(biāo)記
                //如果兩個(gè)相等則說明當(dāng)前線程是最后一個(gè)擴(kuò)容結(jié)束的線程
                //此時(shí)就需要當(dāng)前線程執(zhí)行收尾操作,需要從舊數(shù)組中的尾部開始向頭部節(jié)點(diǎn)遍歷來檢查是否所有的元素節(jié)點(diǎn)都轉(zhuǎn)移了
                //如果都轉(zhuǎn)移了,則將擴(kuò)容后的新數(shù)組設(shè)置為當(dāng)前集合正在使用的數(shù)組,并且計(jì)算下一次擴(kuò)容的閾值
                //如果還有元素節(jié)點(diǎn)沒有轉(zhuǎn)移,當(dāng)前線程則會(huì)將剩下的元素節(jié)點(diǎn)進(jìn)行轉(zhuǎn)移
                //如果兩個(gè)不相等則說明當(dāng)前線程不是最后一個(gè)擴(kuò)容結(jié)束的線程
                //當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引位置的元素節(jié)點(diǎn),并且舊數(shù)組中沒有其他需要轉(zhuǎn)移的節(jié)點(diǎn)了,當(dāng)前線程直接退出擴(kuò)容
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                    return;
                //當(dāng)前線程是最后一個(gè)結(jié)束擴(kuò)容的線程,此時(shí)就需要檢查舊數(shù)組中是否所有的元素節(jié)點(diǎn)都轉(zhuǎn)移了
                finishing = advance = true;
                //從舊數(shù)組的尾部開始檢查
                i = n;
            }
        } else if ((f = tabAt(tab, i)) == null)
            //如果指定索引位置上的節(jié)點(diǎn)為空則直接將舊數(shù)組中該索引位置上的節(jié)點(diǎn)設(shè)置成一個(gè)正在轉(zhuǎn)移的節(jié)點(diǎn)進(jìn)行占位
            //當(dāng)有新的線程要對(duì)該索引位置的節(jié)點(diǎn)操作的時(shí)候則會(huì)發(fā)現(xiàn)該索引位置上的節(jié)點(diǎn)是一個(gè)正在轉(zhuǎn)移的節(jié)點(diǎn)
            //則不會(huì)對(duì)該索引位置上的節(jié)點(diǎn)進(jìn)行操作而是先協(xié)助擴(kuò)容線程進(jìn)行轉(zhuǎn)移
            advance = casTabAt(tab, i, null, fwd);
        else if ((fh = f.hash) == MOVED)
            //指定索引位置上的節(jié)點(diǎn)已經(jīng)被其它線程處理過
            advance = true;
        else {
            //加鎖,防止其它線程對(duì)當(dāng)前需要移動(dòng)的節(jié)點(diǎn)進(jìn)行操作
            synchronized (f) {
                //校驗(yàn)節(jié)點(diǎn)是否變更
                if (tabAt(tab, i) == f) {
                    //ln 低位節(jié)點(diǎn),該節(jié)點(diǎn)放置到新數(shù)組中的索引位置與在舊數(shù)組中的索引位置相同
                    //hn 高位節(jié)點(diǎn),該節(jié)點(diǎn)放置到新數(shù)組中的索引位置是在舊數(shù)組中的索引位置加上舊數(shù)組的長度
                    Node<K, V> ln, hn;
                    //校驗(yàn)節(jié)點(diǎn)的hash值是否大于等于0
                    //如果節(jié)點(diǎn)的hash值大于等于0則說明該索引位置上只有一個(gè)節(jié)點(diǎn)或節(jié)點(diǎn)是一個(gè)鏈表節(jié)點(diǎn)
                    if (fh >= 0) {
                        //使用節(jié)點(diǎn)的hash值與舊數(shù)組的長度進(jìn)行與運(yùn)算
                        //與運(yùn)算后的結(jié)果分為0和1,0則將該節(jié)點(diǎn)放置到低位,1則將節(jié)點(diǎn)放置高位
                        int runBit = fh & n;
                        //避免后續(xù)轉(zhuǎn)移節(jié)點(diǎn)的時(shí)候沒有必要的循環(huán)以及創(chuàng)建節(jié)點(diǎn)
                        Node<K, V> lastRun = f;
                        //遍歷整個(gè)鏈表來決定從那個(gè)節(jié)點(diǎn)開始以及后續(xù)的節(jié)點(diǎn)都是沒有必要遍歷和創(chuàng)建的
                        //而是直接使用指針指向舊數(shù)組中的節(jié)點(diǎn),當(dāng)擴(kuò)容完成之后舊數(shù)組以及舊數(shù)組中部分沒有被指針引用的節(jié)點(diǎn)則會(huì)被回收
                        for (Node<K, V> p = f.next; p != null; p = p.next) {
                            int b = p.hash & n;
                            if (b != runBit) {
                                runBit = b;
                                lastRun = p;
                            }
                        }
                        //沒有必要循環(huán)和創(chuàng)建的節(jié)點(diǎn)應(yīng)該放置到新數(shù)組中的低為還是高位
                        if (runBit == 0) {
                            //低位
                            ln = lastRun;
                            hn = null;
                        } else {
                            //高位
                            hn = lastRun;
                            ln = null;
                        }
                        //從頭節(jié)點(diǎn)開始遍歷將需要重新創(chuàng)建的節(jié)點(diǎn)進(jìn)行創(chuàng)建并添加到高位或低位
                        //在添加到高位或低位的時(shí)候,新創(chuàng)建的節(jié)點(diǎn)使用的是頭插法
                        //如果有節(jié)點(diǎn)不需要重新創(chuàng)建,那不需要重新創(chuàng)建的節(jié)點(diǎn)則會(huì)放到高位或低位節(jié)點(diǎn)的尾部
                        //在上面的時(shí)候如果不需要重新創(chuàng)建的節(jié)點(diǎn)其實(shí)就已經(jīng)放在了ln或hn中
                        //當(dāng)有節(jié)點(diǎn)重新創(chuàng)建了的時(shí)候,則ln或hn設(shè)置為新創(chuàng)建的節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
                        //其實(shí)整體來說就是頭插法,如果說整個(gè)鏈表或部分連續(xù)的鏈表節(jié)點(diǎn)不需要重新創(chuàng)建的時(shí)候還是保持在舊數(shù)組中一樣的順序
                        for (Node<K, V> p = f; p != lastRun; p = p.next) {
                            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);
                            else
                                //高位
                                hn = new Node<K, V>(ph, pk, pv, hn);
                        }
                        //將低位節(jié)點(diǎn)鏈表添加到新數(shù)組中所在的索引位置與舊數(shù)組所在的索引位置相同
                        setTabAt(nextTab, i, ln);
                        //將高位節(jié)點(diǎn)鏈表添加到新數(shù)組中所在的索引位置是在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度
                        setTabAt(nextTab, i + n, hn);
                        //將舊數(shù)組中的索引位置上的節(jié)點(diǎn)設(shè)置為一個(gè)已經(jīng)轉(zhuǎn)移的節(jié)點(diǎn)
                        setTabAt(tab, i, fwd);
                        //繼續(xù)推進(jìn)下一次節(jié)點(diǎn)轉(zhuǎn)移
                        advance = true;
                    //節(jié)點(diǎn)是一個(gè)紅黑樹節(jié)點(diǎn)
                    } else if (f instanceof TreeBin) {
                        TreeBin<K, V> t = (TreeBin<K, V>) f;
                        //低位頭節(jié)點(diǎn)和尾節(jié)點(diǎn)
                        TreeNode<K, V> lo = null, loTail = null;
                        //高位頭節(jié)點(diǎn)和尾節(jié)點(diǎn)
                        TreeNode<K, V> hi = null, hiTail = null;
                        //低位節(jié)點(diǎn)和高位節(jié)點(diǎn)的數(shù)量
                        int lc = 0, hc = 0;
                        //從TreeBin對(duì)象中記錄的鏈表的頭節(jié)點(diǎn)開始遍歷將每一個(gè)節(jié)點(diǎn)分為低位節(jié)點(diǎn)和高位節(jié)點(diǎn)
                        for (Node<K, V> e = t.first; e != null; e = e.next) {
                            int h = e.hash;
                            //構(gòu)造新的樹節(jié)點(diǎn)
                            TreeNode<K, V> p = new TreeNode<K, V>(h, e.key, e.val, null, null);
                            if ((h & n) == 0) {
                                //將構(gòu)造的樹節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)指針指向原低位尾節(jié)點(diǎn)
                                //如果原低尾節(jié)點(diǎn)為空則說明當(dāng)前樹節(jié)點(diǎn)是第一個(gè)節(jié)點(diǎn)
                                //那就將當(dāng)前樹節(jié)點(diǎn)設(shè)置為低位頭節(jié)點(diǎn)
                                if ((p.prev = loTail) == null)
                                    lo = p;
                                else
                                    //原低位尾節(jié)點(diǎn)不為空則將原尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)指針指向當(dāng)前樹節(jié)點(diǎn)
                                    loTail.next = p;
                                //將新構(gòu)造的樹節(jié)點(diǎn)設(shè)置為低位尾節(jié)點(diǎn)
                                loTail = p;
                                //低位節(jié)點(diǎn)的數(shù)量自增
                                ++lc;
                            } else {
                                if ((p.prev = hiTail) == null)
                                    hi = p;
                                else
                                    hiTail.next = p;
                                hiTail = p;
                                ++hc;
                            }
                        }
                        //拆分后的高低位節(jié)點(diǎn)的數(shù)量是否小于等于紅黑樹轉(zhuǎn)鏈表的閾值
                        //如果小于等于則調(diào)用untreeify方法將樹節(jié)點(diǎn)轉(zhuǎn)換為鏈表節(jié)點(diǎn)
                        //如果高位節(jié)點(diǎn)數(shù)量或低位節(jié)點(diǎn)數(shù)量大于閾值則會(huì)校驗(yàn)對(duì)方節(jié)點(diǎn)的數(shù)量是否不等于0
                        //如果說對(duì)方的節(jié)點(diǎn)數(shù)量等于0則說明節(jié)點(diǎn)并沒有拆分為高位或低位節(jié)點(diǎn)
                        //那就使用原本的TreeBin對(duì)象進(jìn)行轉(zhuǎn)移
                        //如果對(duì)方的節(jié)點(diǎn)數(shù)量大于0則說明已經(jīng)拆分為了高低位節(jié)點(diǎn)
                        //此時(shí)就需要將高低位節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹并封裝成一個(gè)TreeBin對(duì)象
                        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;
                        //將低位節(jié)點(diǎn)TreeBin對(duì)象添加到新數(shù)組中所在的索引位置與舊數(shù)組所在的索引位置相同
                        setTabAt(nextTab, i, ln);
                        //將高位節(jié)點(diǎn)TreeBin添加到新數(shù)組中所在的索引位置是在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度
                        setTabAt(nextTab, i + n, hn);
                        //將舊數(shù)組中的索引位置上的節(jié)點(diǎn)設(shè)置為一個(gè)已經(jīng)轉(zhuǎn)移的節(jié)點(diǎn)
                        setTabAt(tab, i, fwd);
                        //繼續(xù)推進(jìn)下一次節(jié)點(diǎn)轉(zhuǎn)移
                        advance = true;
                    }
                }
            }
        }
    }
}

transfer方法中的代碼比較多,我們就一段一段的講解。

int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
    stride = MIN_TRANSFER_STRIDE;
if (nextTab == null) {
    try {
        @SuppressWarnings("unchecked")
        Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1];
        nextTab = nt;
    } catch (Throwable ex) {
        sizeCtl = Integer.MAX_VALUE;
        return;
    }
    nextTable = nextTab;
    transferIndex = n;
}

首先會(huì)通過舊數(shù)組的長度來計(jì)算每個(gè)cpu在轉(zhuǎn)移舊數(shù)組中的節(jié)點(diǎn)所需要負(fù)責(zé)的區(qū)域長度,每個(gè)cpu最少需要負(fù)責(zé)16個(gè)區(qū)域的長度。 nextTab == null則是校驗(yàn)是否有線程已經(jīng)在對(duì)新的數(shù)組進(jìn)行初始化了,如果沒有那當(dāng)前線程則會(huì)去初始化新的數(shù)組,新數(shù)組的長度則是舊數(shù)組的2倍,如果出現(xiàn)異常則說明舊數(shù)組的長度已經(jīng)到達(dá)了數(shù)組最大的容量大小了,此時(shí)就不能再繼續(xù)擴(kuò)容了,數(shù)組最大的容量大小為2的30次方,則是int類型中正整數(shù)最大的2的次方,如果再次進(jìn)行擴(kuò)容的話就會(huì)導(dǎo)致數(shù)組的容量大小變成負(fù)數(shù)。

ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab);
ForwardingNode(Node<K,V>[] tab) {
    super(MOVED, null, null, null);
    this.nextTable = tab;
}

ForwardingNode節(jié)點(diǎn)是一個(gè)占位節(jié)點(diǎn),當(dāng)將舊數(shù)組中指定索引位置上的所有節(jié)點(diǎn)都轉(zhuǎn)移到了新的數(shù)組中,則會(huì)使用該節(jié)點(diǎn)進(jìn)行占位,告知其它線程該索引位置上的節(jié)點(diǎn)都已經(jīng)被轉(zhuǎn)移到了新的數(shù)組中。

while (advance) {
    int nextIndex, nextBound;
    if (--i >= bound || finishing)
        advance = false;
    else if ((nextIndex = transferIndex) <= 0) {
        i = -1;
        advance = false;
    } else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
        bound = nextBound;
        i = nextIndex - 1;
        advance = false;
    }
}

while循環(huán)的作用主要是為每個(gè)線程分配所負(fù)責(zé)的區(qū)域以及推進(jìn)轉(zhuǎn)移的進(jìn)度。

--i >= bound || finishing--i >= bound則是每次轉(zhuǎn)移完一個(gè)索引位置上的節(jié)點(diǎn)的時(shí)候都會(huì)校驗(yàn)一下下一次轉(zhuǎn)移的索引位置的節(jié)點(diǎn)是否已經(jīng)超出當(dāng)前線程負(fù)責(zé)轉(zhuǎn)移的邊界了,而finishing則是校驗(yàn)舊數(shù)組中所有的元素節(jié)點(diǎn)是否都已經(jīng)完成了轉(zhuǎn)移。

(nextIndex = transferIndex) <= 0:剩余需要轉(zhuǎn)移的區(qū)域的長度是否小于等于0,小于等于0則說明舊數(shù)組中的元素節(jié)點(diǎn)已經(jīng)轉(zhuǎn)移完成了,在開始準(zhǔn)備轉(zhuǎn)移的時(shí)候該值為舊數(shù)組的長度,每當(dāng)有一個(gè)線程來協(xié)助擴(kuò)容的時(shí)候則會(huì)從該值中取一部分長度來負(fù)責(zé)轉(zhuǎn)移。

條件三則是根據(jù)開始轉(zhuǎn)移的長度位置與每個(gè)線程需要轉(zhuǎn)移的長度進(jìn)行比較,如果開始轉(zhuǎn)移的長度位置大于每個(gè)線程需要轉(zhuǎn)移的長度,那就使用開始轉(zhuǎn)移的長度位置減去每個(gè)線程需要轉(zhuǎn)移的長度,獲取到當(dāng)前線程需要負(fù)責(zé)轉(zhuǎn)移的索引位置的邊界,再將剩余需要轉(zhuǎn)移的長度存放到transferIndex中,等待其它線程協(xié)助或者說等待當(dāng)前線程轉(zhuǎn)移完所負(fù)責(zé)的區(qū)域之后繼續(xù)轉(zhuǎn)移剩余的長度,如果開始轉(zhuǎn)移的長度位置小于等于每個(gè)線程所需要轉(zhuǎn)移的長度,那就說明當(dāng)前線程自己可以完成轉(zhuǎn)移,不需要其它線程協(xié)助,轉(zhuǎn)移節(jié)點(diǎn)的時(shí)候則是從數(shù)組的尾部向前推進(jìn)。

if (i < 0 || i >= n || i + n >= nextn) {
    int sc;
    if (finishing) {
        nextTable = null;
        table = nextTab;
        sizeCtl = (n << 1) - (n >>> 1);
        return;
    }
    if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
        if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
            return;
        finishing = advance = true;
        i = n;
    }
}

i小于0則說明當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的區(qū)域節(jié)點(diǎn)已經(jīng)轉(zhuǎn)移完成,并且數(shù)組中沒有其它需要轉(zhuǎn)移的節(jié)點(diǎn)了,此時(shí)就需要通過下面的cas操作將sizeCtl中的擴(kuò)容線程數(shù)量減1,線程數(shù)量減1之后會(huì)去校驗(yàn)當(dāng)前線程是否是最后一個(gè)擴(kuò)容的線程,如果不是則說明當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引位置的元素節(jié)點(diǎn)轉(zhuǎn)移,并且舊數(shù)組中沒有其他需要轉(zhuǎn)移的節(jié)點(diǎn)了,當(dāng)前線程直接退出擴(kuò)容,如果是最后一個(gè)擴(kuò)容的線程,此時(shí)就需要當(dāng)前線程執(zhí)行收尾操作,需要從舊數(shù)組中的尾部開始向頭部節(jié)點(diǎn)變量來檢查是否所有的元素節(jié)點(diǎn)都轉(zhuǎn)移了,如果還有沒被轉(zhuǎn)移的元素節(jié)點(diǎn),那當(dāng)前線程則會(huì)去進(jìn)行轉(zhuǎn)移,當(dāng)檢查完成之后會(huì)將新的數(shù)組替換舊的數(shù)組,并計(jì)算下一次擴(kuò)容的閾值。

else if ((f = tabAt(tab, i)) == null)
    advance = casTabAt(tab, i, null, fwd);

如果指定索引位置上的節(jié)點(diǎn)為空則通過cas操作將舊數(shù)組中該索引位置上的節(jié)點(diǎn)設(shè)置成ForwardingNode節(jié)點(diǎn)進(jìn)行占位,告知其它線程該索引位置上的節(jié)點(diǎn)已經(jīng)進(jìn)行了轉(zhuǎn)移,其它線程則不會(huì)對(duì)該索引位置上的節(jié)點(diǎn)進(jìn)行操作,而是先協(xié)助擴(kuò)容線程進(jìn)行節(jié)點(diǎn)轉(zhuǎn)移。

else if ((fh = f.hash) == MOVED)
    advance = true;

索引位置上的節(jié)點(diǎn)的hash值為MOVED,則說明該索引位置上的節(jié)點(diǎn)都已經(jīng)轉(zhuǎn)移了,當(dāng)前線程則繼續(xù)推進(jìn)索引位置轉(zhuǎn)節(jié)點(diǎn)。

synchronized (f) {
    if (tabAt(tab, i) == f) {
        Node<K, V> ln, hn;
        if (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;
                hn = null;
            } else {
                hn = lastRun;
                ln = null;
            }
            for (Node<K, V> p = f; p != lastRun; p = p.next) {
                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);
                else
                    hn = new Node<K, V>(ph, pk, pv, hn);
            }
            setTabAt(nextTab, i, ln);
            setTabAt(nextTab, i + n, hn);
            setTabAt(tab, i, fwd);
            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;
        }
    }
}

我們再看真正轉(zhuǎn)移節(jié)點(diǎn)的代碼,分為轉(zhuǎn)移鏈表節(jié)點(diǎn)和轉(zhuǎn)移紅黑樹節(jié)點(diǎn)。

轉(zhuǎn)移鏈表節(jié)點(diǎn):鏈表節(jié)點(diǎn)會(huì)被分為高位節(jié)點(diǎn)鏈表(hn)低位節(jié)點(diǎn)鏈表(ln),首先通過頭節(jié)點(diǎn)的hash值與舊數(shù)組的長度進(jìn)行與運(yùn)算,與運(yùn)算后的結(jié)果分為0和1,0則將節(jié)點(diǎn)放置到低位,1將該節(jié)點(diǎn)放置到高位,然后遍歷整個(gè)鏈表來決定從那個(gè)節(jié)點(diǎn)開始以及后續(xù)的節(jié)點(diǎn)都是沒有必要重新創(chuàng)建的,而是直接使用指針指向舊數(shù)組中的節(jié)點(diǎn),lastRun指針指向的節(jié)點(diǎn)以及后續(xù)的節(jié)點(diǎn)都是沒有必要?jiǎng)?chuàng)建的,因?yàn)槟愫罄m(xù)的鏈表節(jié)點(diǎn)沒有被拆分為高位或低位,是一個(gè)連續(xù)存放在高位或低位的鏈表節(jié)點(diǎn),所以說不需要重新創(chuàng)建節(jié)點(diǎn),比如說一個(gè)鏈表中的所有節(jié)點(diǎn)都是放在低位節(jié)點(diǎn)中的,那lastRun指針指向的就是鏈表的頭節(jié)點(diǎn),該鏈表中的節(jié)點(diǎn)并沒有改動(dòng),并不需要重新創(chuàng)建節(jié)點(diǎn),而是直接將鏈表中的頭節(jié)點(diǎn)放置到新的數(shù)組中即可,當(dāng)區(qū)分了哪些節(jié)點(diǎn)是不需要重新創(chuàng)建的,則會(huì)將不需要重新創(chuàng)建的節(jié)點(diǎn)的頭節(jié)點(diǎn)lastRun賦值給高位或低位,然后從鏈表的頭節(jié)點(diǎn)開始遍歷將需要?jiǎng)?chuàng)建的節(jié)點(diǎn)進(jìn)行創(chuàng)建并添加到高位鏈表或低位鏈表中,在添加到高位鏈表或低位鏈表的時(shí)候,節(jié)點(diǎn)使用的是頭插法,創(chuàng)建完成之后,低位節(jié)點(diǎn)鏈表會(huì)放置到新數(shù)組中所在的索引位置與節(jié)點(diǎn)在舊數(shù)組中所在的索引位置相同,高位節(jié)點(diǎn)鏈表會(huì)放置到新數(shù)組中所在的索引位置是節(jié)點(diǎn)在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度,當(dāng)高低位節(jié)點(diǎn)鏈表都轉(zhuǎn)移完成之后則會(huì)在舊數(shù)組中該索引位置上添加到一個(gè)占位節(jié)點(diǎn)。

下面的圖中ln則是低位節(jié)點(diǎn)鏈表,hn則是高位節(jié)點(diǎn)鏈表,在某些情況下有些節(jié)點(diǎn)并不需要重新創(chuàng)建,而是使用原來的節(jié)點(diǎn),最差的情況下就是只有鏈表節(jié)點(diǎn)的尾部的一個(gè)節(jié)點(diǎn)不需要重新創(chuàng)建。

紅黑樹節(jié)點(diǎn):從TreeBin對(duì)象中記錄的鏈表的頭節(jié)點(diǎn)開始遍歷,將每一個(gè)節(jié)點(diǎn)轉(zhuǎn)換為新的樹節(jié)點(diǎn),并分為高低位鏈表節(jié)點(diǎn),校驗(yàn)高低位鏈表節(jié)點(diǎn)中的節(jié)點(diǎn)數(shù)量是否小于等于紅黑樹轉(zhuǎn)鏈表的閾值,如果小于等于則會(huì)將高低位鏈表節(jié)點(diǎn)中的所有樹節(jié)點(diǎn)都轉(zhuǎn)換為普通的Node節(jié)點(diǎn),如果不小于等于則會(huì)將高低位鏈表節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹。 如果說高位或低位是一個(gè)鏈表節(jié)點(diǎn)的話,則會(huì)將鏈表的頭節(jié)點(diǎn)放置到新的數(shù)組中,如果是紅黑樹的話,則會(huì)將TreeBin對(duì)象放置到新的數(shù)組中,然后再將舊數(shù)組中的索引位置上添加一個(gè)占位節(jié)點(diǎn)。

注意:其實(shí)在轉(zhuǎn)移舊數(shù)組中的節(jié)點(diǎn)的時(shí)候是有問題的,有可能會(huì)造成節(jié)點(diǎn)數(shù)據(jù)的丟失

線程A獲取到了索引位置上的鏈表節(jié)點(diǎn),頭節(jié)點(diǎn)類型為Node,準(zhǔn)備對(duì)該鏈表節(jié)點(diǎn)加synchronized鎖進(jìn)行轉(zhuǎn)移時(shí),此時(shí)線程B先加了synchronized鎖,對(duì)該索引位置上的鏈表節(jié)點(diǎn)添加了新的節(jié)點(diǎn)并將鏈表節(jié)點(diǎn)轉(zhuǎn)換為了紅黑樹,并將TreeBin對(duì)象放置在了該索引位置上,當(dāng)線程B釋放了鎖之后,線程A獲取到了鎖后去判斷之前獲取到的頭節(jié)點(diǎn)Node是否與索引位置上最新的TreeBin對(duì)象相同,顯然是不相同的,當(dāng)不相同的情況下就會(huì)跳過該索引位置上的節(jié)點(diǎn)的轉(zhuǎn)移,在上面說過,當(dāng)擴(kuò)容完成時(shí),最后一個(gè)線程會(huì)去檢查舊數(shù)組中是否還有節(jié)點(diǎn)沒有轉(zhuǎn)移,如果有則會(huì)進(jìn)行轉(zhuǎn)移,如果說在檢查轉(zhuǎn)移的時(shí)候也遇到了上面類似的問題,刪除節(jié)點(diǎn)的時(shí)候?qū)?code>TreeBin轉(zhuǎn)換為Node,是不是就會(huì)跳過該索引位置上的檢查,從而導(dǎo)致節(jié)點(diǎn)數(shù)據(jù)丟失。

以上就是Java并發(fā)源碼分析ConcurrentHashMap線程集合的詳細(xì)內(nèi)容,更多關(guān)于Java ConcurrentHashMap的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Springboot+Hutool自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏

    Springboot+Hutool自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏

    我們在項(xiàng)目中會(huì)處理敏感數(shù)據(jù)時(shí),通常需要對(duì)這些數(shù)據(jù)進(jìn)行脫敏,本文主要使用了Springboot整合Hutool來自定義注解實(shí)現(xiàn)數(shù)據(jù)脫敏,感興趣的可以理解下
    2023-10-10
  • springboot中的starter使用解析

    springboot中的starter使用解析

    這篇文章主要介紹了springboot中的starter使用解析,引入了starter依賴之后,基礎(chǔ)組件就可以像在spring的bean一樣在項(xiàng)目中使用,那其實(shí)只要找到在哪里加載了這些bean就明白了,需要的朋友可以參考下
    2023-10-10
  • IDEA內(nèi)存調(diào)試插件(好用)

    IDEA內(nèi)存調(diào)試插件(好用)

    本文給大家分享IDEA中一個(gè)很有用的內(nèi)存調(diào)試插件,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下
    2018-02-02
  • Java?ArrayList集合之解鎖數(shù)據(jù)存儲(chǔ)新姿勢

    Java?ArrayList集合之解鎖數(shù)據(jù)存儲(chǔ)新姿勢

    這篇文章主要介紹了Java?ArrayList集合之解鎖數(shù)據(jù)存儲(chǔ)新姿勢,ArrayList是一個(gè)動(dòng)態(tài)數(shù)組,可以自動(dòng)調(diào)整大小,并提供了豐富的操作方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-03-03
  • java:無法訪問org.springframework.boot.SpringApplication

    java:無法訪問org.springframework.boot.SpringApplication

    本文主要介紹了java:無法訪問org.springframework.boot.SpringApplication,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-03-03
  • Java全面細(xì)致講解Wrapper的使用

    Java全面細(xì)致講解Wrapper的使用

    在封裝中有一種特殊的類,能夠把基本的數(shù)據(jù)類型進(jìn)行轉(zhuǎn)換來方便實(shí)際的使用。我們在之前提到的一些數(shù)據(jù)類型,最明顯的特征是所有字母為小寫狀態(tài),那么經(jīng)過Wrapper的包裝后,首字母就變成了大寫。下面我們就這種特殊的封裝類Wrapper的使用
    2022-05-05
  • Java動(dòng)態(tài)顯示文件上傳進(jìn)度實(shí)現(xiàn)代碼

    Java動(dòng)態(tài)顯示文件上傳進(jìn)度實(shí)現(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了Java動(dòng)態(tài)顯示文件上傳進(jìn)度實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • MyBatis-Plus自動(dòng)填充字段的詳細(xì)教程

    MyBatis-Plus自動(dòng)填充字段的詳細(xì)教程

    今天編寫一個(gè)詳細(xì)的教程來介紹如何在?Spring?Boot?項(xiàng)目中使用?MyBatis-Plus?實(shí)現(xiàn)自動(dòng)填充時(shí)間字段(如創(chuàng)建時(shí)間?createTime?和更新時(shí)間?updateTime),可以分為以下幾個(gè)部分,這個(gè)教程將涵蓋從項(xiàng)目配置到自動(dòng)填充的完整過程,需要的朋友可以參考下
    2024-08-08
  • Java中spring boot 字符串判斷是否為空方法小結(jié)

    Java中spring boot 字符串判斷是否為空方法小結(jié)

    這篇文章主要介紹了Java中spring boot字符串判斷是否為空,通過安裝依賴,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-11-11
  • 從dubbo zookeeper注冊地址提取出zookeeper地址的方法

    從dubbo zookeeper注冊地址提取出zookeeper地址的方法

    今天小編就為大家分享一篇關(guān)于從dubbo zookeeper注冊地址提取出zookeeper地址的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12

最新評(píng)論

日韩中文字幕在线播放第二页| 中文字幕高清在线免费播放| 超级福利视频在线观看| 中国老熟女偷拍第一页| 日本黄色三级高清视频| 国产福利小视频免费观看| 九色精品视频在线播放| 在线新三级黄伊人网| 社区自拍揄拍尻屁你懂的| 国产精品久久久黄网站| 亚洲在线观看中文字幕av| 久久久久久久亚洲午夜综合福利| 不卡精品视频在线观看| 岛国毛片视频免费在线观看| 4个黑人操素人视频网站精品91| av在线shipin| 午夜精品一区二区三区福利视频| 亚洲推理片免费看网站| 少妇高潮无套内谢麻豆| 一区二区三区久久中文字幕| 香蕉91一区二区三区| 亚洲国产中文字幕啊啊啊不行了| 97超碰最新免费在线观看| jiujiure精品视频在线| 不戴胸罩引我诱的隔壁的人妻| 93视频一区二区三区| 97瑟瑟超碰在线香蕉| 91老师蜜桃臀大屁股| 啊啊啊想要被插进去视频| 91欧美在线免费观看| av天堂中文免费在线| 青青青青视频在线播放| 99一区二区在线观看| 欧美 亚洲 另类综合| sw137 中文字幕 在线| 在线观看免费岛国av| 日本美女性生活一级片| 久精品人妻一区二区三区| sejizz在线视频| 人妻少妇性色欲欧美日韩| 中文字幕成人日韩欧美| av完全免费在线观看av| 白白操白白色在线免费视频| 精品久久久久久久久久中文蒉| 天堂av在线官网中文| 玖玖一区二区在线观看| 看一级特黄a大片日本片黑人| 黑人乱偷人妻中文字幕| 老司机你懂得福利视频| 一本久久精品一区二区| 青青尤物在线观看视频网站| 亚洲av黄色在线网站| 青青青国产片免费观看视频| 在线国产中文字幕视频| 黄色录像鸡巴插进去| 懂色av蜜桃a v| 国产精品一二三不卡带免费视频| 熟妇一区二区三区高清版| 青青青青青免费视频| 亚洲精品久久综合久| 国产97视频在线精品| 国产在线观看免费人成短视频| 啊啊好大好爽啊啊操我啊啊视频 | 九色视频在线观看免费| 三级黄色亚洲成人av| 97少妇精品在线观看| 久草视频在线免播放| 老师啊太大了啊啊啊尻视频| 精品av国产一区二区三区四区 | 亚洲另类伦春色综合小| 制服丝袜在线人妻中文字幕| av新中文天堂在线网址| 91超碰青青中文字幕| 91av精品视频在线| 亚洲Av无码国产综合色区| 三级等保密码要求条款| 欧美成人一二三在线网| 国产综合高清在线观看| 色综合天天综合网国产成人| 97黄网站在线观看| 亚洲日本一区二区久久久精品| 小穴多水久久精品免费看| 亚洲一区二区三区久久午夜| 国产成人自拍视频在线免费观看| 午夜美女少妇福利视频| 国产大学生援交正在播放| 插逼视频双插洞国产操逼插洞| 日韩欧美一级精品在线观看| 日韩视频一区二区免费观看| 欧美80老妇人性视频| 伊人网中文字幕在线视频| 国产刺激激情美女网站| 久碰精品少妇中文字幕av| 女人精品内射国产99| 亚洲av日韩高清hd| 美女骚逼日出水来了| 2021天天色天天干| 天天做天天干天天操天天射| 精品suv一区二区69| 亚洲推理片免费看网站| 好太好爽好想要免费| 狠狠的往里顶撞h百合| 国产精品自拍视频大全| 国产性生活中老年人视频网站| 欧美一级色视频美日韩| 51国产成人精品视频| 色天天天天射天天舔| 亚洲欧洲一区二区在线观看| 中文字幕无码一区二区免费| av天堂资源最新版在线看| 天天干狠狠干天天操| 可以免费看的www视频你懂的| 日比视频老公慢点好舒服啊| 午夜精品一区二区三区城中村| 午夜国产免费福利av| 欧美精品免费aaaaaa| 国产91嫩草久久成人在线视频| 成人av在线资源网站| 精品亚洲在线免费观看| 亚洲另类图片蜜臀av| 久青青草视频手机在线免费观看| 国产又大又黄免费观看| 这里只有精品双飞在线播放| www,久久久,com| 国产亚洲天堂天天一区| 天天日天天爽天天干| 在线亚洲天堂色播av电影| 蜜桃久久久久久久人妻| 青青热久免费精品视频在线观看| jul—619中文字幕在线| 插小穴高清无码中文字幕| 色天天天天射天天舔| 国产精品久久久久网| 成人av天堂丝袜在线观看| 日本欧美视频在线观看三区| av天堂中文免费在线| 日韩中文字幕精品淫| 天天射夜夜操综合网| 日韩精品二区一区久久| 精品人人人妻人人玩日产欧| 特黄老太婆aa毛毛片| 中文字幕无码一区二区免费| 国产美女一区在线观看| 国产精品入口麻豆啊啊啊| 中文字幕午夜免费福利视频| 99热99这里精品6国产| 亚洲伊人av天堂有码在线| 东京热男人的av天堂| 国产成人精品亚洲男人的天堂| 同居了嫂子在线播高清中文| xxx日本hd高清| 亚洲成人激情av在线| 精品一区二区三区午夜| 成年人啪啪视频在线观看| 国产精品黄页网站视频| 国产精品中文av在线播放| 一区二区三区日本伦理| 性生活第二下硬不起来| 免费成人va在线观看| 这里只有精品双飞在线播放| 人妻另类专区欧美制服| 91久久人澡人人添人人爽乱| 五色婷婷综合狠狠爱| 亚洲av无硬久久精品蜜桃| 天天日天天干天天插舔舔| 日韩精品一区二区三区在线播放| 国产日本欧美亚洲精品视| 天天日天天爽天天爽| 扒开腿挺进肉嫩小18禁视频| 阴茎插到阴道里面的视频| 亚洲国产40页第21页| 九色视频在线观看免费| 欧美交性又色又爽又黄麻豆| 青青草国内在线视频精选| 成熟丰满熟妇高潮xx×xx | 久久久久久久久久久免费女人| 男生舔女生逼逼的视频| 男人靠女人的逼视频| 免费看国产av网站| 偷拍自拍视频图片免费| 熟女91pooyn熟女| 大香蕉玖玖一区2区| 9色在线视频免费观看| 91中文字幕免费在线观看| 人妻少妇av在线观看| 日本丰满熟妇大屁股久久| 99精品国产自在现线观看| 欧美第一页在线免费观看视频| 999九九久久久精品| 天天摸天天日天天操| 欧美成人一二三在线网| 国产麻豆91在线视频| 绝顶痉挛大潮喷高潮无码 | 成人蜜桃美臀九一一区二区三区| 欧美精品资源在线观看| 久久久久久久久久久久久97| 美女张开腿让男生操在线看| 97国产在线av精品| 一区二区三区四区五区性感视频| 大鸡巴后入爆操大屁股美女| 777奇米久久精品一区| 日本一道二三区视频久久| 日韩欧美高清免费在线| 在线观看911精品国产| 农村胖女人操逼视频| 国产精品视频男人的天堂| 偷偷玩弄新婚人妻h视频| 揄拍成人国产精品免费看视频| 国产精品午夜国产小视频| 男女啪啪视频免费在线观看| 午夜av一区二区三区| 成人亚洲国产综合精品| 黄色无码鸡吧操逼视频| 99re国产在线精品| 中文字幕无码一区二区免费| 91精品啪在线免费| 亚洲av琪琪男人的天堂| 国产精品伦理片一区二区| 全国亚洲男人的天堂| 亚洲一区制服丝袜美腿| 2021久久免费视频| 亚洲精品三级av在线免费观看| 精品亚洲中文字幕av| 好了av中文字幕在线| 日本高清在线不卡一区二区| 早川濑里奈av黑人番号| 亚洲国产精品中文字幕网站| 在线免费观看靠比视频的网站| 久久久久久97三级| 中国黄色av一级片| 97精品成人一区二区三区| 日本真人性生活视频免费看| 免费看国产av网站| 免费费一级特黄真人片| 免费看国产av网站| 亚洲综合在线视频可播放| 色吉吉影音天天干天天操 | 国际av大片在线免费观看| 黑人巨大精品欧美视频| 一区二区在线观看少妇| 麻豆性色视频在线观看| 亚洲一级特黄特黄黄色录像片| 黑人借宿ntr人妻的沦陷2| 色吉吉影音天天干天天操| 18禁网站一区二区三区四区| 欧美精品黑人性xxxx| 亚洲一区自拍高清免费视频| 日本熟女50视频免费| 老司机你懂得福利视频| 亚洲区欧美区另类最新章节| 青青青青青青青青青青草青青 | 欧美黄片精彩在线免费观看| 青青草原网站在线观看| 福利午夜视频在线观看| 免费高清自慰一区二区三区网站| 2022国产精品视频| 国产亚州色婷婷久久99精品| 岛国毛片视频免费在线观看| 国产一线二线三线的区别在哪| 亚洲午夜电影之麻豆| 伊人情人综合成人久久网小说| 日日摸夜夜添夜夜添毛片性色av| 成年人该看的视频黄免费| 成人国产激情自拍三区| 国产综合精品久久久久蜜臀| 欧美日韩在线精品一区二区三| 久久久久久cao我的性感人妻| 欧美va不卡视频在线观看| 亚洲国产最大av综合| 丝袜美腿欧美另类 中文字幕| 中文字幕日韩91人妻在线| yy96视频在线观看| 五色婷婷综合狠狠爱| 91老师蜜桃臀大屁股| 亚洲伊人色一综合网| 中文字幕日韩无敌亚洲精品 | 午夜福利人人妻人人澡人人爽| 免费观看丰满少妇做受| 91福利视频免费在线观看| 视频在线亚洲一区二区| 日美女屁股黄邑视频| 不卡日韩av在线观看| 一级黄片大鸡巴插入美女| 国产女人露脸高潮对白视频| 欧美黑人性暴力猛交喷水| 美日韩在线视频免费看| 亚洲 清纯 国产com| 午夜久久香蕉电影网| 日本三极片中文字幕| 青青青青操在线观看免费| 亚洲av男人天堂久久| 日本韩国免费福利精品| 六月婷婷激情一区二区三区| 亚洲一区二区激情在线| 黑人巨大精品欧美视频| 亚洲公开视频在线观看| 亚洲 清纯 国产com| 成年人午夜黄片视频资源| 国产密臀av一区二区三| 五月天色婷婷在线观看视频免费| 亚洲国产成人av在线一区| 老有所依在线观看完整版| 巨乳人妻日下部加奈被邻居中出| 国产精品女邻居小骚货| 91 亚洲视频在线观看| 在线免费观看亚洲精品电影| 中文字幕日韩无敌亚洲精品| 好吊视频—区二区三区| 青青青aaaa免费| 沙月文乃人妻侵犯中文字幕在线 | 和邻居少妇愉情中文字幕| 欧美女同性恋免费a| 欧美日韩中文字幕欧美| 黄色的网站在线免费看| 亚洲 国产 成人 在线| 97资源人妻免费在线视频| 2019av在线视频| av中文字幕在线观看第三页| 亚洲少妇高潮免费观看| 最新97国产在线视频| 青青草在观免费国产精品| 适合午夜一个人看的视频| 欧美爆乳肉感大码在线观看| 天天艹天天干天天操| 人妻少妇性色欲欧美日韩| 韩国黄色一级二级三级| 黄色视频在线观看高清无码| 日韩伦理短片在线观看| 亚洲最大黄 嗯色 操 啊| 亚洲伊人色一综合网| 男人在床上插女人视频| 精品成人啪啪18免费蜜臀| 成人高清在线观看视频| 绝色少妇高潮3在线观看| 欧美日韩精品永久免费网址 | 黄色中文字幕在线播放| 国产精品一二三不卡带免费视频 | 五十路熟女人妻一区二| 又粗又长 明星操逼小视频 | 中文字日产幕乱六区蜜桃| tube69日本少妇| 最新欧美一二三视频| 久久久久国产成人精品亚洲午夜| 免费福利av在线一区二区三区| 欧美成人一二三在线网| 亚洲精品一线二线在线观看| 91自产国产精品视频| av中文字幕国产在线观看| 精品欧美一区二区vr在线观看| 边摸边做超爽毛片18禁色戒| 亚洲无线观看国产高清在线| 国产妇女自拍区在线观看| 一区二区三区激情在线| 午夜精品在线视频一区| 蜜桃久久久久久久人妻| 激情人妻校园春色亚洲欧美 | 大鸡巴操b视频在线| 伊人开心婷婷国产av| 日噜噜噜夜夜噜噜噜天天噜噜噜| 18禁精品网站久久| 亚洲视频在线视频看视频在线| 欧美区一区二区三视频| 免费无码人妻日韩精品一区二区| 成人av中文字幕一区| 亚洲成人黄色一区二区三区| av一区二区三区人妻| 少妇深喉口爆吞精韩国| 粉嫩小穴流水视频在线观看| 日韩美女综合中文字幕pp| 热久久只有这里有精品| 亚洲图片偷拍自拍区| 99久久中文字幕一本人| 特大黑人巨大xxxx| 美女在线观看日本亚洲一区| 很黄很污很色的午夜网站在线观看| 国产综合高清在线观看| 99婷婷在线观看视频| 国产综合高清在线观看| 插小穴高清无码中文字幕| 九色视频在线观看免费| 午夜国产免费福利av| 青青青爽视频在线播放| 国产高清精品极品美女| 91久久精品色伊人6882| 日韩a级黄色小视频| 少妇一区二区三区久久久| 中文字幕在线永久免费播放| 91麻豆精品秘密入口在线观看| 国产高清97在线观看视频| 亚洲美女高潮喷浆视频| 国产精品人妻熟女毛片av久| 老熟妇xxxhd老熟女| 天天插天天色天天日| 亚洲高清国产一区二区三区| 都市激情校园春色狠狠| 婷婷六月天中文字幕| 不卡一区一区三区在线| 91久久精品色伊人6882| 激情人妻校园春色亚洲欧美| 久久尻中国美女视频| 亚洲精品 欧美日韩| 欧美成人一二三在线网| 日本黄色三级高清视频| 韩国女主播精品视频网站| 免费在线黄色观看网站| 欧美成人精品在线观看| 久久久超爽一二三av| 91色九色porny| 国产美女午夜福利久久| 青青青青青青青在线播放视频| 中文字幕在线欧美精品| 欧美黑人性暴力猛交喷水| 亚洲av在线观看尤物| 韩国黄色一级二级三级| 99精品一区二区三区的区| 亚洲国产精品免费在线观看| 中文字幕欧美日韩射射一| 超污视频在线观看污污污| 亚洲午夜电影在线观看| 国产在线一区二区三区麻酥酥| 亚洲男人的天堂a在线| 日韩欧美中文国产在线| 亚洲综合在线观看免费| 日本一二三区不卡无| 直接观看免费黄网站| 日曰摸日日碰夜夜爽歪歪| 人妻少妇亚洲一区二区| 91中文字幕免费在线观看| 亚洲蜜臀av一区二区三区九色 | 免费观看丰满少妇做受| 日本人妻少妇18—xx| 一级A一级a爰片免费免会员| 在线观看视频网站麻豆| mm131美女午夜爽爽爽| 亚洲特黄aaaa片| av一区二区三区人妻| 精品国产午夜视频一区二区| 日本特级片中文字幕| 日本熟妇喷水xxx| 97国产在线观看高清| 欧洲欧美日韩国产在线| 午夜精品一区二区三区更新| 免费成人av中文字幕| 国产福利在线视频一区| 亚洲国产精品久久久久蜜桃| 国产精品国产三级国产午| 国产+亚洲+欧美+另类| 成年人该看的视频黄免费| 少妇深喉口爆吞精韩国| 亚洲欧洲一区二区在线观看| 久久www免费人成一看片| 亚洲精品午夜久久久久| h国产小视频福利在线观看| 小穴多水久久精品免费看| 91精品免费久久久久久| 青青青激情在线观看视频| 亚洲区美熟妇久久久久| 男人插女人视频网站| 综合激情网激情五月五月婷婷| 硬鸡巴动态操女人逼视频| 后入美女人妻高清在线| 国产精品视频男人的天堂| 久久精品在线观看一区二区| 超级福利视频在线观看| 一区二区免费高清黄色视频| 无套猛戳丰满少妇人妻| 男生舔女生逼逼的视频| 欧美一级色视频美日韩| 亚洲国产成人无码麻豆艾秋| 亚洲天天干 夜夜操| 亚洲免费国产在线日韩| 国产刺激激情美女网站| 欧美精产国品一二三产品区别大吗| 亚洲福利午夜久久久精品电影网| 美味人妻2在线播放| 老熟妇xxxhd老熟女| 精品欧美一区二区vr在线观看| 成年美女黄网站18禁久久| 黄色视频成年人免费观看| 偷拍自拍国产在线视频| 人妻无码中文字幕专区| 被大鸡吧操的好舒服视频免费| 天天夜天天日天天日| 欧美熟妇一区二区三区仙踪林| 国产视频在线视频播放| 日韩美女精品视频在线观看网站| 大鸡八强奸视频在线观看| 国产成人小视频在线观看无遮挡| 91九色porny蝌蚪国产成人| 五十路在线观看完整版| 日韩欧美制服诱惑一区在线| 少妇一区二区三区久久久| av天堂加勒比在线| 国产成人无码精品久久久电影| 99精品亚洲av无码国产另类| 国产精彩对白一区二区三区| 亚洲精品国品乱码久久久久| 中国黄色av一级片| 国产成人自拍视频播放 | 日本女人一级免费片| 最新国产精品拍在线观看| 国产大学生援交正在播放| 黑人乱偷人妻中文字幕| 精品高跟鞋丝袜一区二区| heyzo蜜桃熟女人妻| 国产清纯美女al在线| 丰满的子国产在线观看| 日曰摸日日碰夜夜爽歪歪| 亚洲成人激情av在线| 男大肉棒猛烈插女免费视频| 66久久久久久久久久久| 天天干天天操天天玩天天射| 国产精品人妻66p| 最新激情中文字幕视频| 9国产精品久久久久老师| 亚洲 清纯 国产com| 欧美日韩v中文在线| 无码精品一区二区三区人| 人妻激情图片视频小说| 班长撕开乳罩揉我胸好爽| 不卡日韩av在线观看| 亚洲中文字幕综合小综合| 美女吃鸡巴操逼高潮视频| 欧美亚洲少妇福利视频| 在线观看av观看av| 成年人该看的视频黄免费| 57pao国产一区二区| 国产又粗又硬又大视频| 中文字幕熟女人妻久久久| nagger可以指黑人吗| 午夜激情高清在线观看| 青青在线视频性感少妇和隔壁黑丝| 亚洲成人黄色一区二区三区| 嫩草aⅴ一区二区三区| 国产午夜亚洲精品麻豆| 国产伦精品一区二区三区竹菊| 国产综合视频在线看片| 孕妇奶水仑乱A级毛片免费看| 亚洲 欧美 精品 激情 偷拍| 欧美亚洲少妇福利视频| 国产免费高清视频视频| 57pao国产一区二区| 2022国产综合在线干| 91免费放福利在线观看| 97超碰最新免费在线观看| 欧美综合婷婷欧美综合| 精品一区二区三区欧美| 中文字幕在线免费第一页| 欧美80老妇人性视频| 国产又粗又猛又爽又黄的视频在线 | 欧美亚洲一二三区蜜臀| 国产一区二区火爆视频| 亚洲欧美国产综合777| 红杏久久av人妻一区| 日本黄在免费看视频| 国内精品在线播放第一页| 天天摸天天日天天操| 亚洲精品福利网站图片| 日本中文字幕一二区视频| 在线免费观看99视频| 婷婷激情四射在线观看视频| 偷拍自拍国产在线视频| lutube在线成人免费看| 中文字幕在线观看极品视频| 午夜久久久久久久精品熟女| 免费在线观看视频啪啪| av网址国产在线观看| 人妻少妇性色欲欧美日韩| 亚洲av极品精品在线观看| 玖玖一区二区在线观看| 啪啪啪啪啪啪啪免费视频| 国产精品人妻66p| 一区二区视频视频视频| 久久丁香婷婷六月天| 超级福利视频在线观看| 女生自摸在线观看一区二区三区| 一二三区在线观看视频| 青青草亚洲国产精品视频| 一区二区三区日韩久久| 欧美黑人性猛交xxxxⅹooo| 色哟哟国产精品入口| 久久精品久久精品亚洲人| 少妇露脸深喉口爆吞精| 插小穴高清无码中文字幕| 91精品国产综合久久久蜜| 任我爽精品视频在线播放| 午夜在线观看一区视频| 成年午夜免费无码区| 国产av欧美精品高潮网站| 中文字幕午夜免费福利视频| 亚洲偷自拍高清视频| 亚洲一级特黄特黄黄色录像片| 日本又色又爽又黄又粗| 日韩伦理短片在线观看| 夜夜骑夜夜操夜夜奸| 婷婷六月天中文字幕| 精品美女在线观看视频在线观看| 红杏久久av人妻一区| 午夜精品在线视频一区| 亚洲美女美妇久久字幕组| 日本韩国亚洲综合日韩欧美国产 | 38av一区二区三区| 十八禁在线观看地址免费| 亚洲精品麻豆免费在线观看| 91 亚洲视频在线观看| 亚洲精品麻豆免费在线观看| 中国老熟女偷拍第一页| 一区二区三区另类在线| 亚洲av可乐操首页| 久青青草视频手机在线免费观看 | 日韩成人综艺在线播放| 丝袜长腿第一页在线| 久久美欧人妻少妇一区二区三区| sspd152中文字幕在线| 韩国AV无码不卡在线播放| 999九九久久久精品| 免费大片在线观看视频网站| 天天日天天天天天天天天天天| 视频一区二区在线免费播放| 97人妻无码AV碰碰视频| 18禁精品网站久久| 亚洲国产精品黑丝美女| 国产三级精品三级在线不卡| 亚洲精品欧美日韩在线播放| 污污小视频91在线观看| 福利在线视频网址导航| 国产精品视频一区在线播放| 性色av一区二区三区久久久| 五月精品丁香久久久久福利社| 国产成人精品久久二区91| 国产黄色高清资源在线免费观看| 超碰97免费人妻麻豆| 2021国产一区二区| 天天射夜夜操综合网| 亚洲一级av无码一级久久精品| 激情伦理欧美日韩中文字幕| 一区二区麻豆传媒黄片 | 护士小嫩嫩又紧又爽20p| 伊人成人综合开心网| 亚洲av无码成人精品区辽| 亚洲精品av在线观看| 午夜大尺度无码福利视频 | 亚洲综合在线观看免费| 亚洲蜜臀av一区二区三区九色| 午夜久久香蕉电影网| 国产又粗又猛又爽又黄的视频在线| 又粗又长 明星操逼小视频| 丁香花免费在线观看中文字幕| 晚上一个人看操B片| 亚洲欧美成人综合在线观看| 91‖亚洲‖国产熟女| 亚洲第一伊人天堂网| 93视频一区二区三区| 成人综合亚洲欧美一区| 在线成人日韩av电影| 日韩精品二区一区久久| 亚洲 国产 成人 在线| 99re6热在线精品| 国产视频在线视频播放| 9久在线视频只有精品| 亚洲激情偷拍一区二区| 女同性ⅹxx女同hd| 人妻少妇亚洲精品中文字幕| 99精品国产免费久久| 成人亚洲国产综合精品| 最新激情中文字幕视频| 天天操天天干天天艹| 欧美另类重口味极品在线观看| 欧美xxx成人在线| 亚洲最大免费在线观看| 51国产成人精品视频| 91精品免费久久久久久| 成人网18免费视频版国产 | 亚洲av极品精品在线观看| 国产精品日韩欧美一区二区| 2021久久免费视频| 欧美伊人久久大香线蕉综合| 自拍偷拍日韩欧美亚洲| 天天色天天操天天舔| 在线国产日韩欧美视频| 国产激情av网站在线观看| 免费在线看的黄片视频| 老熟妇xxxhd老熟女| 美女视频福利免费看| 青青青青草手机在线视频免费看 | 在线国产日韩欧美视频| 9l人妻人人爽人人爽| caoporn蜜桃视频| 精品suv一区二区69| 免费高清自慰一区二区三区网站| 韩国AV无码不卡在线播放| 天天摸天天干天天操科普| 班长撕开乳罩揉我胸好爽| 亚洲精品国偷自产在线观看蜜桃| 日本高清成人一区二区三区| 天天操,天天干,天天射| 亚洲丝袜老师诱惑在线观看| 在线播放 日韩 av| 亚洲一级av大片免费观看| 人妻3p真实偷拍一二区| 在线免费视频 自拍| 自拍偷拍日韩欧美亚洲| 75国产综合在线视频| 91免费放福利在线观看| 男生舔女生逼逼的视频| 动色av一区二区三区| 欧美80老妇人性视频| gay gay男男瑟瑟在线网站| 中文字幕1卡1区2区3区| 亚洲免费视频欧洲免费视频| 日韩无码国产精品强奸乱伦| 2020久久躁狠狠躁夜夜躁| 天天日天天干天天舔天天射| 91精品高清一区二区三区| 中国熟女一区二区性xx| 欧美亚洲少妇福利视频| 中文人妻AV久久人妻水| 日本精品视频不卡一二三| 亚洲熟色妇av日韩熟色妇在线| 亚洲视频在线视频看视频在线| 亚洲伊人av天堂有码在线| 91国产资源在线视频| 亚洲国产免费av一区二区三区| 综合精品久久久久97| 日本少妇的秘密免费视频| 日韩美女搞黄视频免费| 午夜精品一区二区三区城中村| 欧美美女人体视频一区| 日韩欧美国产精品91| 国产麻豆剧果冻传媒app| 综合一区二区三区蜜臀| 夜色福利视频在线观看| 亚洲一级av大片免费观看| 在线观看视频网站麻豆| 蜜桃专区一区二区在线观看| 欧美日韩亚洲国产无线码| 中文字幕日本人妻中出| 日韩三级黄色片网站| 久久久久只精品国产三级| 人人人妻人人澡人人| 在线新三级黄伊人网| 亚洲1069综合男同| 中文字幕在线乱码一区二区| 福利午夜视频在线合集| 天天日天天干天天舔天天射| 中文字幕成人日韩欧美| 我想看操逼黄色大片| 国产精选一区在线播放| 青青草原色片网站在线观看| 精品久久婷婷免费视频| 日韩欧美一级aa大片| 亚洲欧美人精品高清| 都市家庭人妻激情自拍视频| 日辽宁老肥女在线观看视频| 视频啪啪啪免费观看| 亚洲免费在线视频网站| 班长撕开乳罩揉我胸好爽| 女人精品内射国产99| 青青草精品在线视频观看| 国产精品久久久久久久女人18| 国产清纯美女al在线| 黄色av网站免费在线| www日韩毛片av| 国产综合精品久久久久蜜臀| 欧美老鸡巴日小嫩逼| 午夜毛片不卡免费观看视频 | 天天摸天天干天天操科普| 偷拍自拍国产在线视频| 亚洲老熟妇日本老妇| h国产小视频福利在线观看| 欧美成人猛片aaaaaaa| 欧美爆乳肉感大码在线观看| av中文字幕电影在线看| 啪啪啪啪啪啪啪啪啪啪黄色| 99视频精品全部15| a v欧美一区=区三区| 亚洲高清视频在线不卡| 亚洲综合一区二区精品久久| 精彩视频99免费在线| 亚洲免费成人a v| 女人精品内射国产99| 中文字幕人妻av在线观看| 99久久久无码国产精品性出奶水| 中文字幕日韩无敌亚洲精品 | 99re久久这里都是精品视频| 久久www免费人成一看片| 最新91九色国产在线观看| asmr福利视频在线观看| 大胆亚洲av日韩av| 欧美视频不卡一区四区| 国产真实乱子伦a视频| 天堂资源网av中文字幕| 激情国产小视频在线| 经典av尤物一区二区| 秋霞午夜av福利经典影视| 欧美va亚洲va天堂va| 青青青青青青青青青青草青青 | 日日摸夜夜添夜夜添毛片性色av| 午夜大尺度无码福利视频| 日韩三级电影华丽的外出| 蜜桃视频17c在线一区二区| 91免费黄片可看视频| 青青在线视频性感少妇和隔壁黑丝 | 粉嫩小穴流水视频在线观看| 男人的天堂在线黄色| 国产精品探花熟女在线观看| 春色激情网欧美成人| 国产欧美日韩第三页| 少妇人妻100系列| 五月色婷婷综合开心网4438| 大鸡吧插入女阴道黄色片| 国产精品系列在线观看一区二区| 青青草亚洲国产精品视频| 麻豆性色视频在线观看| 色在线观看视频免费的| 美女小视频网站在线| 亚洲美女高潮喷浆视频| 狠狠躁夜夜躁人人爽天天久天啪| 六月婷婷激情一区二区三区| 国产真实乱子伦a视频| 一区二区三区四区视频| 亚洲高清自偷揄拍自拍| 国产在线自在拍91国语自产精品| 精品高跟鞋丝袜一区二区| 热久久只有这里有精品| 欧美日韩v中文在线| 中国把吊插入阴蒂的视频| 国产黄色高清资源在线免费观看| 天天日天天添天天爽| 国产黑丝高跟鞋视频在线播放| 色爱av一区二区三区| 自拍偷拍,中文字幕| 大屁股熟女一区二区三区| 亚洲一区二区激情在线| 免费看国产又粗又猛又爽又黄视频| 99精品一区二区三区的区| 亚洲伊人久久精品影院一美女洗澡| 国产一区二区欧美三区| 3344免费偷拍视频| 美女福利视频导航网站| av一本二本在线观看| 成人亚洲精品国产精品| 国产精品自拍在线视频| 骚货自慰被发现爆操| 98视频精品在线观看| 人人妻人人人操人人人爽| 亚洲成人三级在线播放| 欧美一区二区三区激情啪啪啪| 一区二区熟女人妻视频| 开心 色 六月 婷婷| aaa久久久久久久久| sspd152中文字幕在线| 亚洲公开视频在线观看| 亚洲av琪琪男人的天堂| 亚洲另类综合一区小说| 亚洲特黄aaaa片| 亚洲福利午夜久久久精品电影网| 亚洲欧美激情人妻偷拍| 在线国产中文字幕视频| 国产精品探花熟女在线观看| 日本乱人一区二区三区| 天天干天天搞天天摸| 爱爱免费在线观看视频| 欧美成人黄片一区二区三区| 深田咏美亚洲一区二区| av在线观看网址av| 青青草国内在线视频精选| 青青青国产免费视频| 啪啪啪18禁一区二区三区| 大肉大捧一进一出好爽在线视频 | 亚洲成人国产综合一区| 中文字幕+中文字幕| 中文字幕第三十八页久久 | 亚洲人妻av毛片在线| 自拍偷拍亚洲精品第2页| 扒开让我视频在线观看| 国产美女精品福利在线| 亚洲精品在线资源站| 99国产精品窥熟女精品| 国产三级精品三级在线不卡| 88成人免费av网站| 日韩美女综合中文字幕pp| 97色视频在线观看| 婷婷六月天中文字幕| 亚洲成人情色电影在线观看| 久草视频福利在线首页| 欧美xxx成人在线| 免费啪啪啪在线观看视频| 最新日韩av传媒在线| av一区二区三区人妻| 国产女人叫床高潮大片视频| 欧美亚洲中文字幕一区二区三区| xxx日本hd高清| 97欧洲一区二区精品免费| 人妻最新视频在线免费观看| 天天干夜夜操天天舔| 亚洲一区二区三区偷拍女厕91| 天天日天天干天天搡| 日韩影片一区二区三区不卡免费| 国产精品人妻熟女毛片av久| 精品人妻伦一二三区久 | 亚洲日产av一区二区在线| 欧美男人大鸡吧插女人视频| 欧美地区一二三专区| 99精品视频在线观看婷婷| 少妇露脸深喉口爆吞精| 欧美精品黑人性xxxx| 免费岛国喷水视频在线观看| 毛片一级完整版免费| 精品人人人妻人人玩日产欧| 91色网站免费在线观看| 午夜福利资源综合激情午夜福利资 | 二区中出在线观看老师| 97国产在线观看高清| 国产精品久久久久国产三级试频| 999热精品视频在线| 精品av久久久久久久| 天天操天天插天天色| 2025年人妻中文字幕乱码在线| 午夜久久久久久久精品熟女| 日韩欧美中文国产在线| xxx日本hd高清| 国产日韩精品免费在线| 女同性ⅹxx女同hd| 亚洲欧美国产麻豆综合| 中文字幕奴隷色的舞台50| 在线观看911精品国产| 北条麻妃肉色丝袜视频| 日本精品美女在线观看| 久草福利电影在线观看| 日比视频老公慢点好舒服啊| 国产精品精品精品999| 国产揄拍高清国内精品对白| 91免费黄片可看视频| jiujiure精品视频在线| a v欧美一区=区三区| 天天色天天舔天天射天天爽| 一区二区三区四区五区性感视频| 国语对白xxxx乱大交| 超碰中文字幕免费观看| 亚洲欧美一卡二卡三卡| 国产又色又刺激在线视频 | 国产黄色片蝌蚪九色91| 中文字日产幕乱六区蜜桃| 日本一道二三区视频久久 | 99精品免费久久久久久久久a| 在线网站你懂得老司机| 在线观看av2025| 欧美一区二区三区乱码在线播放 | 黄片大全在线观看观看| 日本免费午夜视频网站| 偷青青国产精品青青在线观看| 日日夜夜精品一二三| 最新中文字幕免费视频| 超黄超污网站在线观看| 亚洲公开视频在线观看| 人人妻人人爽人人澡人人精品| 性色av一区二区三区久久久| 91中文字幕最新合集| 岛国一区二区三区视频在线| 国产妇女自拍区在线观看| 精品一区二区三区在线观看| 精品视频一区二区三区四区五区| 国产麻豆剧传媒精品国产av蜜桃| 初美沙希中文字幕在线| 97色视频在线观看| 青青在线视频性感少妇和隔壁黑丝| 男人在床上插女人视频| 摧残蹂躏av一二三区| 啊啊好慢点插舔我逼啊啊啊视频| 天天操天天操天天碰| 99久久99一区二区三区| 国产又粗又猛又爽又黄的视频在线| 国产chinesehd精品麻豆| 婷婷激情四射在线观看视频| 涩涩的视频在线观看视频| 亚洲av日韩高清hd| 一区二区在线视频中文字幕| 91国内精品自线在拍白富美| 久久综合老鸭窝色综合久久| 国产一级麻豆精品免费| 在线亚洲天堂色播av电影| 女生被男生插的视频网站| 亚洲一区二区人妻av| 阴茎插到阴道里面的视频| 中国无遮挡白丝袜二区精品| aaa久久久久久久久| 天天日天天干天天干天天日| av中文字幕福利网| 岛国毛片视频免费在线观看| 国产精品三级三级三级| 天天干夜夜操啊啊啊| 亚洲国产在线精品国偷产拍 | 偷拍自拍 中文字幕| 日本黄色三级高清视频| 一区二区三区四区中文| 丰满的继坶3中文在线观看| 91高清成人在线视频| 污污小视频91在线观看| 国产熟妇人妻ⅹxxxx麻豆| 热久久只有这里有精品| 日韩精品中文字幕播放| 亚洲 欧美 自拍 偷拍 在线| 92福利视频午夜1000看| 无忧传媒在线观看视频| 久久麻豆亚洲精品av| 青青青青青免费视频| 久久久久久性虐视频| 亚洲国产免费av一区二区三区| 丝袜美腿欧美另类 中文字幕| 五十路息与子猛烈交尾视频| 57pao国产一区二区| 日韩在线视频观看有码在线| 黑人3p华裔熟女普通话| 亚洲一区二区三区精品乱码| 亚洲人妻30pwc| 亚洲欧美福利在线观看| 亚洲一区二区三区久久午夜| 国产精彩对白一区二区三区| 高清成人av一区三区| 亚洲av男人天堂久久| 精品国产在线手机在线| 成人sm视频在线观看| 婷婷激情四射在线观看视频| 偷拍自拍亚洲美腿丝袜| 大屁股肉感人妻中文字幕在线| 国产精品系列在线观看一区二区| 操日韩美女视频在线免费看| 欧美成人综合色在线噜噜| 欧美国品一二三产区区别| 蜜桃臀av蜜桃臀av| 免费高清自慰一区二区三区网站| 一区二区三区 自拍偷拍| 亚洲区美熟妇久久久久| 99亚洲美女一区二区三区| 亚洲 国产 成人 在线| huangse网站在线观看| 国产janese在线播放| 天天干天天操天天摸天天射| 精品亚洲国产中文自在线| 国产黄色a级三级三级三级| av大全在线播放免费| 亚洲老熟妇日本老妇| 亚洲成av人无码不卡影片一| 孕妇奶水仑乱A级毛片免费看| 91小伙伴中女熟女高潮| 亚洲国产美女一区二区三区软件 | 久久午夜夜伦痒痒想咳嗽P| 51国产偷自视频在线播放| 亚洲自拍偷拍综合色| 亚洲av男人天堂久久| 精品91高清在线观看| 亚洲高清自偷揄拍自拍| 天天射夜夜操综合网| 国产视频网站国产视频| 天天日天天敢天天干| 免费看国产av网站| 欧美日韩高清午夜蜜桃大香蕉| 一区二区在线观看少妇| 亚洲精品成人网久久久久久小说| 老司机深夜免费福利视频在线观看| 熟女视频一区,二区,三区| 521精品视频在线观看| 国产在线免费观看成人| 黄色在线观看免费观看在线| 久久精品国产23696| 成人av在线资源网站| 91小伙伴中女熟女高潮| 爱爱免费在线观看视频| 我想看操逼黄色大片| 无码中文字幕波多野不卡| 伊人网中文字幕在线视频| 蜜臀av久久久久久久| 久久精品国产23696| 日日操综合成人av| av久久精品北条麻妃av观看| 青青草人人妻人人妻| 亚洲综合另类精品小说| 姐姐的朋友2在线观看中文字幕 | 美女日逼视频免费观看| 亚洲欧洲av天堂综合| 欲乱人妻少妇在线视频裸| 久久久久久久一区二区三| lutube在线成人免费看| 人妻少妇精品久久久久久 | 在线视频国产欧美日韩| 手机看片福利盒子日韩在线播放| 视频久久久久久久人妻| 中文字幕中文字幕 亚洲国产| 日本黄色特一级视频| 免费无毒热热热热热热久| 日本美女成人在线视频| 99精品国产免费久久| 男人在床上插女人视频| 国产伊人免费在线播放| www骚国产精品视频| 中文字幕日韩精品就在这里| 91国内精品自线在拍白富美| 欧美另类z0z变态| 激情五月婷婷综合色啪| 中文字幕日韩91人妻在线| 天堂女人av一区二区| 欧美日韩人妻久久精品高清国产| 亚洲一区二区三区久久午夜| 天天摸天天亲天天舔天天操天天爽| 在线观看免费岛国av| 中文字幕 人妻精品| 亚洲午夜精品小视频| 亚洲熟女久久久36d| 欧美另类一区二区视频| 日本免费视频午夜福利视频| 日本三极片中文字幕| 亚洲伊人久久精品影院一美女洗澡 | 91精品免费久久久久久| 成人综合亚洲欧美一区| 青青草人人妻人人妻| 国产视频网站一区二区三区| 婷婷久久久综合中文字幕| 91成人在线观看免费视频| 国产欧美日韩第三页| 日韩欧美一级黄片亚洲| 久久www免费人成一看片| 美女福利视频导航网站| 青青热久免费精品视频在线观看| 中文字幕 人妻精品| 亚洲综合另类欧美久久| 黄色三级网站免费下载| 这里只有精品双飞在线播放| 啊啊好大好爽啊啊操我啊啊视频 | 国产精品成人xxxx| 亚洲国产精品免费在线观看| 超碰中文字幕免费观看| 国产a级毛久久久久精品| 啊啊啊视频试看人妻| 精品一区二区三区三区88| 少妇被强干到高潮视频在线观看| 精品亚洲中文字幕av| 啊用力插好舒服视频| 亚洲 清纯 国产com| 亚洲精品三级av在线免费观看| 99热久久这里只有精品8| 欧美另类重口味极品在线观看| 国产真实乱子伦a视频| 狠狠的往里顶撞h百合| 亚洲老熟妇日本老妇| 午夜蜜桃一区二区三区| av中文字幕在线导航| 亚洲自拍偷拍精品网| 精品乱子伦一区二区三区免费播| 美女日逼视频免费观看| 成人伊人精品色xxxx视频| 国产精品成久久久久三级蜜臀av| 免费69视频在线看| 久久丁香婷婷六月天| 成人30分钟免费视频| 亚洲欧美激情人妻偷拍| asmr福利视频在线观看| 午夜dv内射一区区| 人妻熟女中文字幕aⅴ在线 | 全国亚洲男人的天堂| 中文字幕日韩精品日本| 天堂av狠狠操蜜桃| 91国语爽死我了不卡| 国产福利在线视频一区| 中文字幕奴隷色的舞台50| 国产成人精品亚洲男人的天堂| 在线观看国产免费麻豆| 婷婷六月天中文字幕| 99re国产在线精品| 亚洲成人av一区久久| 2021国产一区二区| av黄色成人在线观看| 成人免费公开视频无毒| 又色又爽又黄又刺激av网站| 真实国模和老外性视频| 亚洲精品午夜久久久久| 国产成人精品午夜福利训2021| 精品成人啪啪18免费蜜臀| 日韩欧美一级精品在线观看| 天天日天天透天天操| 超碰97人人澡人人| 日韩人妻丝袜中文字幕| 久久这里只有精品热视频| 亚洲精品国品乱码久久久久| 欧美成人精品在线观看| 一区二区视频视频视频| 欧美成一区二区三区四区| 不卡日韩av在线观看| 欧美中文字幕一区最新网址| 亚洲熟女女同志女同| 国产又大又黄免费观看| 不卡一区一区三区在线| 在线免费观看视频一二区| 免费看国产av网站| 在线免费观看日本伦理| 最近中文2019年在线看| 丰满的继坶3中文在线观看| 国产使劲操在线播放| 亚洲成人精品女人久久久| 久久精品亚洲国产av香蕉| 天天操,天天干,天天射| 老司机深夜免费福利视频在线观看| 精品久久久久久高潮| 欧美亚洲少妇福利视频| 黑人乱偷人妻中文字幕| 久草视频福利在线首页| 1区2区3区4区视频在线观看| 黄色的网站在线免费看| 日韩a级黄色小视频| 日本韩国免费福利精品| 成人av中文字幕一区| 18禁污污污app下载| 久久这里只有精品热视频 | 婷婷久久一区二区字幕网址你懂得| 国产真实乱子伦a视频 | 久久精品国产23696| 在线观看的a站 最新| 毛茸茸的大外阴中国视频| 小泽玛利亚视频在线观看| 51国产偷自视频在线播放| 老司机福利精品免费视频一区二区| 欲乱人妻少妇在线视频裸| av天堂资源最新版在线看| 亚洲国际青青操综合网站| 亚洲欧美一卡二卡三卡| 国内精品在线播放第一页| 成年人免费看在线视频| 午夜福利人人妻人人澡人人爽 | 人妻3p真实偷拍一二区| 自拍偷拍亚洲欧美在线视频| 新97超碰在线观看| 一本久久精品一区二区| 天天日天天敢天天干| 精品国产污污免费网站入口自 | 国产精选一区在线播放| 夫妻在线观看视频91| 直接能看的国产av| 丰满少妇翘臀后进式| 中文字幕高清资源站| caoporn蜜桃视频| 18禁污污污app下载| 国产精品国产三级国产午| 93精品视频在线观看| 久久精品国产23696| 99精品视频之69精品视频| 亚国产成人精品久久久| 换爱交换乱高清大片| 亚洲高清国产一区二区三区| 日本中文字幕一二区视频| 福利国产视频在线观看| 亚洲欧美另类自拍偷拍色图| 中文字幕在线观看国产片| 97资源人妻免费在线视频| 538精品在线观看视频| 视频一区二区综合精品| 一区二区三区蜜臀在线| 亚洲高清国产一区二区三区| 国产成人自拍视频在线免费观看| 性感美女高潮视频久久久| 一区二区三区的久久的蜜桃的视频 | av黄色成人在线观看| 日本阿v视频在线免费观看| 欧美日韩精品永久免费网址| 伊人精品福利综合导航| 少妇人妻100系列| 大胸性感美女羞爽操逼毛片| 天天操天天污天天射| 精品区一区二区三区四区人妻| 高潮喷水在线视频观看| 国产精品自拍在线视频| 久久机热/这里只有| 精品av久久久久久久| 啪啪啪啪啪啪啪啪啪啪黄色| 日本人妻精品久久久久久| 久久久极品久久蜜桃| 国产成人精品久久二区91| 日韩精品中文字幕福利| 中国视频一区二区三区| 天天射夜夜操综合网| 夜色福利视频在线观看| 天天操天天干天天艹| 欧美一区二区三区啪啪同性| 亚洲图片偷拍自拍区| 亚洲精品在线资源站| 国产午夜亚洲精品麻豆| 91she九色精品国产| 亚洲欧美激情国产综合久久久| 狍和女人的王色毛片| 激情五月婷婷综合色啪| 久草视频 久草视频2| 亚洲嫩模一区二区三区| 午夜久久香蕉电影网| 2019av在线视频| 性欧美日本大妈母与子| 国产精品久久久黄网站| 天天干天天操天天摸天天射| 青青青青青操视频在线观看| 免费一级黄色av网站| 久久精品国产999| 11久久久久久久久久久| 男女第一次视频在线观看| 中文字幕第一页国产在线| 大香蕉大香蕉在线有码 av| 涩爱综合久久五月蜜臀| 日本后入视频在线观看| 无码精品一区二区三区人| 新婚人妻聚会被中出| 免费看国产av网站| 99精品一区二区三区的区| 国产又粗又黄又硬又爽| 97人妻总资源视频| 国产精品亚洲а∨天堂免| 自拍偷拍亚洲精品第2页| 动漫精品视频在线观看| 午夜精品亚洲精品五月色| 亚洲精品三级av在线免费观看| 免费观看丰满少妇做受| 日韩欧美一级精品在线观看| 中文字幕无码一区二区免费| 天天通天天透天天插| 日韩av中文在线免费观看| 国产午夜亚洲精品不卡在线观看| 高清成人av一区三区| 日本韩国免费福利精品| 啪啪啪啪啪啪啪啪啪啪黄色| 在线视频自拍第三页| avjpm亚洲伊人久久| 人人超碰国字幕观看97| 日本真人性生活视频免费看| 中文字幕熟女人妻久久久| 夜色福利视频在线观看| 亚洲美女自偷自拍11页| 蜜桃臀av蜜桃臀av| 日韩欧美在线观看不卡一区二区| 可以免费看的www视频你懂的| 免费十精品十国产网站| 手机看片福利盒子日韩在线播放| 天美传媒mv视频在线观看| 欧美日韩情色在线观看| 91精品综合久久久久3d动漫| 国产综合精品久久久久蜜臀| 天堂资源网av中文字幕| 欧美伊人久久大香线蕉综合| 中文字幕人妻三级在线观看| 最新黄色av网站在线观看| 天天日天天干天天干天天日| 国产亚洲天堂天天一区| 国产精品视频一区在线播放| 色秀欧美视频第一页| 性色蜜臀av一区二区三区| 亚洲午夜精品小视频| 国产成人精品一区在线观看| 亚洲中文字幕综合小综合| 五月精品丁香久久久久福利社| 国产在线观看免费人成短视频| 国产精品久久久久久久精品视频| 天天干夜夜操啊啊啊| 亚洲中文字字幕乱码| 久久久极品久久蜜桃| 91老师蜜桃臀大屁股| 这里只有精品双飞在线播放| 国产一区二区欧美三区 | 午夜精品一区二区三区更新| 婷婷综合亚洲爱久久| 在线视频自拍第三页| 天天日天天日天天射天天干| 熟女人妻在线中出观看完整版| 家庭女教师中文字幕在线播放| 任我爽精品视频在线播放| 国产精品成人xxxx| 国产麻豆剧果冻传媒app| 国产综合视频在线看片| 99精品免费久久久久久久久a| 2025年人妻中文字幕乱码在线| 中文字幕高清免费在线人妻 | 欧美在线一二三视频| 2018最新中文字幕在线观看| 人妻少妇亚洲一区二区| 只有精品亚洲视频在线观看| 神马午夜在线观看视频| 自拍偷拍vs一区二区三区| 午夜毛片不卡在线看| 青青青青青免费视频| 亚洲在线一区二区欧美| 蜜桃视频在线欧美一区| 动漫美女的小穴视频| 亚洲午夜高清在线观看| 黄色中文字幕在线播放| 99热这里只有国产精品6| 中文字幕在线免费第一页| 91成人精品亚洲国产| 日本少妇的秘密免费视频| 极品粉嫩小泬白浆20p主播| 综合一区二区三区蜜臀| 国产精品亚洲在线观看| 在线观看视频一区麻豆| 搡老妇人老女人老熟女| 午夜影院在线观看视频羞羞羞| 啊啊啊想要被插进去视频| 青青青视频自偷自拍38碰| 又粗又硬又猛又爽又黄的| 亚洲人人妻一区二区三区| 国产久久久精品毛片| 亚洲图片欧美校园春色| 日韩a级精品一区二区| 亚洲精品久久视频婷婷| 国产麻豆国语对白露脸剧情 | av大全在线播放免费| 亚洲一区二区三区久久受| 五色婷婷综合狠狠爱| 一区二区在线视频中文字幕| 无码精品一区二区三区人| 青青青青在线视频免费观看| 国产精品黄页网站视频| 亚洲推理片免费看网站| 亚国产成人精品久久久| 久久尻中国美女视频| 亚洲自拍偷拍综合色| 少妇人妻久久久久视频黄片| 成人在线欧美日韩国产| av一区二区三区人妻| 丰满的继坶3中文在线观看| 视频久久久久久久人妻| 亚洲午夜电影之麻豆| 午夜精品福利一区二区三区p | 亚洲午夜福利中文乱码字幕| 亚洲福利精品福利精品福利| 国产在线一区二区三区麻酥酥| 日本一区美女福利视频| 玩弄人妻熟妇性色av少妇| 99久久激情婷婷综合五月天| 亚洲一级av无码一级久久精品| 韩国三级aaaaa高清视频| 任你操视频免费在线观看| 中出中文字幕在线观看| 搡老熟女一区二区在线观看| 青青热久免费精品视频在线观看| av在线播放国产不卡| 久久午夜夜伦痒痒想咳嗽P| 91中文字幕最新合集| 亚洲综合一区成人在线| 操日韩美女视频在线免费看| 美日韩在线视频免费看| 韩国男女黄色在线观看| 91色秘乱一区二区三区| 中文字幕中文字幕人妻| 大香蕉大香蕉大香蕉大香蕉大香蕉| 国产第一美女一区二区三区四区| 亚欧在线视频你懂的| 亚洲福利精品福利精品福利| 动漫黑丝美女的鸡巴| 2018在线福利视频| 在线观看一区二区三级| 亚洲欧美色一区二区| 日日夜夜精品一二三| 岛国毛片视频免费在线观看| 爱有来生高清在线中文字幕| 毛片av在线免费看| 我想看操逼黄色大片| 老司机99精品视频在线观看| 北条麻妃肉色丝袜视频| 最后99天全集在线观看| 天天想要天天操天天干| 国产污污污污网站在线 | 亚洲av可乐操首页| 欧美 亚洲 另类综合| 中文字幕高清在线免费播放| av网址国产在线观看| 91香蕉成人app下载| 视频 一区二区在线观看| 亚洲av日韩高清hd| 国产亚洲精品视频合集| 99国内精品永久免费视频| 一区二区三区蜜臀在线| 国产精品久久久久网| 91人妻精品一区二区久久| 搡老熟女一区二区在线观看| 国产亚洲国产av网站在线| 亚洲卡1卡2卡三卡四老狼| 91精品国产综合久久久蜜| 色哟哟国产精品入口| 亚洲福利午夜久久久精品电影网| 99热久久这里只有精品8| 黄色的网站在线免费看| 午夜毛片不卡在线看| 人妻熟女在线一区二区| 91色秘乱一区二区三区| 国产品国产三级国产普通话三级| 日本成人不卡一区二区| 2012中文字幕在线高清| 亚洲一级av无码一级久久精品| 亚洲视频乱码在线观看| 亚洲一区制服丝袜美腿| 97人妻总资源视频| 97超碰国语国产97超碰| 麻豆性色视频在线观看| 亚洲在线免费h观看网站| 一区二区三区麻豆福利视频| 欧美成人猛片aaaaaaa| 沙月文乃人妻侵犯中文字幕在线| 天天射夜夜操综合网| 成年人啪啪视频在线观看| 中文字幕日韩人妻在线三区| 四虎永久在线精品免费区二区| 中文字幕在线欧美精品| 欧洲精品第一页欧洲精品亚洲| 涩涩的视频在线观看视频| 天天做天天干天天操天天射| 久久精品亚洲成在人线a| 一级A一级a爰片免费免会员| 蜜桃专区一区二区在线观看| 美女张开腿让男生操在线看| 精品乱子伦一区二区三区免费播| 亚洲欧美一区二区三区电影| 国产中文精品在线观看| 免费在线看的黄片视频| huangse网站在线观看| 91老熟女连续高潮对白| 亚洲 欧美 自拍 偷拍 在线| 国产激情av网站在线观看| 欧美一区二区三区乱码在线播放 | 欧美色呦呦最新网址| 亚洲福利精品视频在线免费观看| 好吊视频—区二区三区| 国产美女精品福利在线| 亚洲国产美女一区二区三区软件| 自拍偷拍一区二区三区图片| 亚洲一区二区三区久久午夜| 19一区二区三区在线播放| 亚洲精品在线资源站| 精品人妻一二三区久久| 亚洲粉嫩av一区二区三区| 国产午夜福利av导航| 亚洲国产成人最新资源| 少妇与子乱在线观看| 2022国产综合在线干| 日美女屁股黄邑视频| 视频 一区二区在线观看| 18禁网站一区二区三区四区| 老司机在线精品福利视频| 2012中文字幕在线高清| 国产1区,2区,3区| 亚洲国产40页第21页| 亚洲欧美激情人妻偷拍| 精品久久久久久久久久久a√国产 日本女大学生的黄色小视频 | 天天摸天天亲天天舔天天操天天爽| 91免费观看在线网站| 亚洲欧美另类自拍偷拍色图| 1769国产精品视频免费观看| 日本18禁久久久久久| 午夜激情久久不卡一区二区| 中文字幕 码 在线视频| 污污小视频91在线观看| 久久久久久久久久一区二区三区| 国产在线观看免费人成短视频| 大陆av手机在线观看| 日韩美女搞黄视频免费| 日本韩国亚洲综合日韩欧美国产| 夜色17s精品人妻熟女| 成人av亚洲一区二区| 播放日本一区二区三区电影| 天天草天天色天天干| 青青草人人妻人人妻| 中文字幕一区二区三区人妻大片 | 日本熟女精品一区二区三区| 18禁美女无遮挡免费| 亚洲av自拍偷拍综合| 国产夫妻视频在线观看免费| 亚洲护士一区二区三区| 99婷婷在线观看视频| 精品成人午夜免费看| 亚洲精品乱码久久久本| 国产精品国色综合久久| 好吊操视频这里只有精品| 亚洲图库另类图片区| 粉嫩欧美美人妻小视频| 免费黄页网站4188| 亚洲国产香蕉视频在线播放| 国内自拍第一页在线观看| 岛国青草视频在线观看| 亚洲一区二区三区久久午夜| 91老熟女连续高潮对白| 男人天堂色男人av| 国产大鸡巴大鸡巴操小骚逼小骚逼| 人妻丝袜av在线播放网址| 欧美日韩一区二区电影在线观看| 亚洲自拍偷拍综合色| 欧美精品亚洲精品日韩在线| av手机在线观播放网站| 中国黄色av一级片| 亚洲男人让女人爽的视频| 无码精品一区二区三区人| 欧美亚洲偷拍自拍色图| av在线免费中文字幕| 国产午夜无码福利在线看| 亚欧在线视频你懂的| 国产精品亚洲а∨天堂免| 性感美女福利视频网站| 后入美女人妻高清在线| 一二三中文乱码亚洲乱码one| 欧美另类重口味极品在线观看| 日本av在线一区二区三区| 一区二区三区四区中文| 自拍偷区二区三区麻豆| 青青青aaaa免费| 免费黄高清无码国产| 亚洲男人的天堂a在线| 99久久激情婷婷综合五月天| 国产清纯美女al在线| 天天摸天天干天天操科普| 香港一级特黄大片在线播放 | 青青尤物在线观看视频网站| 日本黄在免费看视频| 秋霞午夜av福利经典影视| 女同性ⅹxx女同h偷拍| 国产老熟女伦老熟妇ⅹ| 亚洲男人让女人爽的视频| 中文字幕第三十八页久久| 久久久久久九九99精品| 亚洲综合另类精品小说| mm131美女午夜爽爽爽| 中文字幕人妻三级在线观看 | okirakuhuhu在线观看| www日韩毛片av| 亚洲伊人av天堂有码在线| 欧美特色aaa大片| av黄色成人在线观看| 亚洲第一黄色在线观看| 成人av中文字幕一区| av俺也去在线播放| 日本成人一区二区不卡免费在线| 欧美va不卡视频在线观看| 人妻丝袜榨强中文字幕| 久久久人妻一区二区| 欧洲亚洲欧美日韩综合| 99re6热在线精品| 成人av天堂丝袜在线观看| 国产精品自偷自拍啪啪啪| av视网站在线观看| 日本熟女50视频免费| 黄片色呦呦视频免费看| 午夜青青草原网在线观看| 综合页自拍视频在线播放| 亚洲人人妻一区二区三区| www久久久久久久久久久| japanese日本熟妇另类| 亚洲av黄色在线网站| 女生自摸在线观看一区二区三区| 99一区二区在线观看| 综合国产成人在线观看| 亚洲精品一线二线在线观看| 国产精品久久久久久美女校花| 97人妻无码AV碰碰视频| 男女啪啪啪啪啪的网站| 久久久精品欧洲亚洲av| 亚洲1区2区3区精华液| 任你操视频免费在线观看| 青青青青青青青在线播放视频| 天天操天天爽天天干| 婷婷久久久综合中文字幕| 中文字幕一区二区亚洲一区| 99热久久这里只有精品| 亚洲欧美在线视频第一页| 在线国产精品一区二区三区| 直接能看的国产av| 亚洲免费国产在线日韩| 国产极品精品免费视频 | 中文字幕视频一区二区在线观看| 欧美精品欧美极品欧美视频| 免费黄色成人午夜在线网站| 国产又粗又硬又猛的毛片视频| 9色在线视频免费观看| 成人高潮aa毛片免费| 最新的中文字幕 亚洲| 岛国毛片视频免费在线观看| 亚洲成人熟妇一区二区三区| 蜜桃臀av蜜桃臀av| avjpm亚洲伊人久久| 亚洲av可乐操首页| 亚洲成人国产综合一区| 日本免费一级黄色录像| 亚洲免费va在线播放| 无码国产精品一区二区高潮久久4| 免费看高清av的网站| 人妻自拍视频中国大陆| 男人和女人激情视频| 亚洲2021av天堂| 91p0rny九色露脸熟女| 久久久久国产成人精品亚洲午夜| av天堂资源最新版在线看| 天天日天天日天天擦| 懂色av之国产精品| 一区二区三区久久中文字幕| 成年人免费看在线视频| 综合页自拍视频在线播放| 中文字幕av熟女人妻| 久久丁香花五月天色婷婷| 无码中文字幕波多野不卡| jiuse91九色视频| 在线免费观看av日韩| 亚洲在线免费h观看网站| av黄色成人在线观看| 久碰精品少妇中文字幕av| 日本人妻欲求不满中文字幕| 久久久久久久99精品| 40道精品招牌菜特色| 国产一区二区欧美三区| 免费在线福利小视频| 黑人3p华裔熟女普通话| 动漫精品视频在线观看| 欧美日韩在线精品一区二区三| 国产成人精品福利短视频| 大屁股肉感人妻中文字幕在线| 99热国产精品666| 久久机热/这里只有| 中文字幕在线永久免费播放| 国产va在线观看精品| 天天干天天操天天插天天日| 中文字幕最新久久久| av森泽佳奈在线观看 | 偷拍自拍视频图片免费| 开心 色 六月 婷婷| 亚洲午夜在线视频福利| 国产一区成人在线观看视频| 在线观看操大逼视频| 天天干狠狠干天天操| 中文字幕一区二区人妻电影冢本| 日本熟女精品一区二区三区| 亚洲激情偷拍一区二区| 国产91嫩草久久成人在线视频| 久久一区二区三区人妻欧美| 蝴蝶伊人久久中文娱乐网| 成人动漫大肉棒插进去视频| 蜜桃视频17c在线一区二区| 国产麻豆剧果冻传媒app| 亚洲午夜精品小视频| 日本熟女精品一区二区三区| 成年人午夜黄片视频资源| 亚洲高清一区二区三区视频在线| 日本熟女精品一区二区三区| 特一级特级黄色网片| 蜜桃视频17c在线一区二区| 免费看国产又粗又猛又爽又黄视频| 欧美日本国产自视大全| 午夜在线观看岛国av,com| 欧美美女人体视频一区| 1024久久国产精品| 99久久久无码国产精品性出奶水| 国产麻豆国语对白露脸剧情| 亚洲欧美日韩视频免费观看| 无码精品一区二区三区人| 亚洲 自拍 色综合图| 精品国产在线手机在线| 青青伊人一精品视频| 日曰摸日日碰夜夜爽歪歪| 人妻熟女在线一区二区| 黑人变态深video特大巨大| av一本二本在线观看| 成人高清在线观看视频| 99精品国自产在线人| 色综合久久久久久久久中文| 天天艹天天干天天操| 天天日天天日天天射天天干| 精品欧美一区二区vr在线观看| 国产品国产三级国产普通话三级| 久久久极品久久蜜桃| 欧美viboss性丰满| 亚洲综合乱码一区二区| 成人av在线资源网站| 青青草成人福利电影| 久久精品36亚洲精品束缚| 亚洲欧美日韩视频免费观看| 欧美精品欧美极品欧美视频| 啊用力插好舒服视频| 性色蜜臀av一区二区三区| 黑人巨大的吊bdsm| 阴茎插到阴道里面的视频| 免费黄页网站4188| 欧美亚洲少妇福利视频| 超碰公开大香蕉97| 天堂资源网av中文字幕| 国产污污污污网站在线| 黑人巨大精品欧美视频| 欧美精品激情在线最新观看视频| 精品国产乱码一区二区三区乱| 中文字幕 人妻精品| 亚洲免费视频欧洲免费视频| 成年女人免费播放视频| 中文字幕 码 在线视频| 在线视频免费观看网| 2022精品久久久久久中文字幕| 亚洲国产精品免费在线观看| 9久在线视频只有精品| 久久精品国产亚洲精品166m| 欧美一区二区三区在线资源| 大胆亚洲av日韩av| 日韩欧美制服诱惑一区在线| 中文字幕无码一区二区免费| 中文字幕+中文字幕| 老熟妇凹凸淫老妇女av在线观看| 国产黄色大片在线免费播放| 激情国产小视频在线| 久久综合老鸭窝色综合久久| 黄色在线观看免费观看在线| 成人av中文字幕一区| 欧美黑人与人妻精品| avjpm亚洲伊人久久| 午夜在线观看一区视频| 男人操女人的逼免费视频| 亚洲一区久久免费视频| 精品黑人一区二区三区久久国产 | 啊啊好大好爽啊啊操我啊啊视频 | 亚洲午夜精品小视频| 日韩一区二区电国产精品| 中文亚洲欧美日韩无线码| 中文字幕乱码人妻电影| 国产av福利网址大全| 久久久久久cao我的性感人妻| 亚洲av色图18p| 性欧美激情久久久久久久| 动漫美女的小穴视频| 国产美女一区在线观看| 黄色黄色黄片78在线| 欧美偷拍亚洲一区二区| 亚洲偷自拍高清视频| 无码日韩人妻精品久久| 超级碰碰在线视频免费观看| 午夜久久久久久久99| 亚洲欧美激情国产综合久久久 | 香蕉91一区二区三区| 天天色天天爱天天爽| 国产 在线 免费 精品| 久久精品视频一区二区三区四区| 久草电影免费在线观看| 美女骚逼日出水来了| 国产亚洲精品品视频在线| 91麻豆精品传媒国产黄色片| 动漫av网站18禁| AV无码一区二区三区不卡| av手机在线观播放网站| 久久精品久久精品亚洲人| 亚洲一区二区三区精品乱码| 经典av尤物一区二区| 国产精品视频男人的天堂| 少妇人妻100系列| 亚洲 图片 欧美 图片| 一区二区在线视频中文字幕| 日本黄色三级高清视频| 人人妻人人爽人人澡人人精品| 天天日天天添天天爽| 亚洲日产av一区二区在线| 久久艹在线观看视频| 和邻居少妇愉情中文字幕| 青青草人人妻人人妻| 9久在线视频只有精品| 视频一区二区在线免费播放| 国产精品免费不卡av| 韩国一级特黄大片做受| 无码国产精品一区二区高潮久久4| 青娱乐最新视频在线| 国产第一美女一区二区三区四区 | 91麻豆精品91久久久久同性| 国产日韩一区二区在线看| 天天干天天啪天天舔| 嫩草aⅴ一区二区三区| 国产午夜激情福利小视频在线| 2020久久躁狠狠躁夜夜躁 | 日本乱人一区二区三区| 欧美在线精品一区二区三区视频| 亚洲图库另类图片区| 日韩精品激情在线观看| 国产又粗又硬又大视频| 人人妻人人爽人人添夜| 国产一区二区神马久久| 激情啪啪啪啪一区二区三区| 青青青青青操视频在线观看| 日本人妻少妇18—xx| 亚洲一区二区三区久久午夜 | 精品首页在线观看视频| 福利国产视频在线观看| 亚洲av日韩高清hd| 亚洲精品国产久久久久久| 欧美日韩一级黄片免费观看| 日本啪啪啪啪啪啪啪| 五十路熟女av天堂| 亚洲一区二区三区av网站| 红桃av成人在线观看| 99久久超碰人妻国产| 男人和女人激情视频| 超级福利视频在线观看| 一区二区在线视频中文字幕| 综合一区二区三区蜜臀| 最新国产精品网址在线观看| 中文字幕 人妻精品| 岛国黄色大片在线观看| 国产伦精品一区二区三区竹菊| 精品91高清在线观看| 国产亚洲视频在线二区| 日韩熟女av天堂系列| 欧美精产国品一二三产品价格| 好男人视频在线免费观看网站| 51国产成人精品视频| 日本五十路熟新垣里子| 亚洲图库另类图片区| 国产成人精品久久二区91| 国产91久久精品一区二区字幕| 高潮视频在线快速观看国家快速| 成年午夜影片国产片| 老熟妇凹凸淫老妇女av在线观看| 人妻少妇亚洲精品中文字幕| 亚洲天堂av最新网址| 韩国男女黄色在线观看| 亚洲久久午夜av一区二区| 91色秘乱一区二区三区| 狠狠的往里顶撞h百合| 99精品视频之69精品视频 | 亚洲av无女神免非久久| 干逼又爽又黄又免费的视频| 日韩成人性色生活片| 一区二区熟女人妻视频| rct470中文字幕在线| 92福利视频午夜1000看| 农村胖女人操逼视频| 成人乱码一区二区三区av| 亚洲欧美成人综合在线观看| 中文字幕中文字幕人妻| 青娱乐蜜桃臀av色| 在线观看黄色成年人网站| 操操网操操伊剧情片中文字幕网| 日韩欧美高清免费在线| 欧美一区二区三区在线资源 | 岛国青草视频在线观看| 精品人妻一二三区久久| 亚洲国产美女一区二区三区软件| 国产一区二区欧美三区| 亚洲欧美成人综合在线观看| 亚洲免费av在线视频| 亚洲av极品精品在线观看| 婷婷五月亚洲综合在线| 亚洲激情av一区二区| 亚洲国产精品免费在线观看| 日韩欧美高清免费在线| 伊人精品福利综合导航| 婷婷午夜国产精品久久久| 黄色av网站免费在线| 专门看国产熟妇的网站| 蜜桃视频入口久久久| AV天堂一区二区免费试看| 中国把吊插入阴蒂的视频| 天天操天天插天天色| 一区二区三区在线视频福利| 人人超碰国字幕观看97| 欧美视频综合第一页| 999九九久久久精品| 中文字幕av男人天堂| 人妻少妇精品久久久久久| 乱亲女秽乱长久久久| aiss午夜免费视频| 在线观看免费av网址大全| 偷拍美女一区二区三区| 亚洲伊人久久精品影院一美女洗澡| 中文字幕人妻熟女在线电影| 成年午夜影片国产片| 男人的天堂在线黄色| 绝色少妇高潮3在线观看| 欧美另类重口味极品在线观看| 国产性生活中老年人视频网站| 国产亚洲视频在线二区| 欧洲欧美日韩国产在线| 青青草亚洲国产精品视频| 成年人黄色片免费网站| 国产密臀av一区二区三| 国产精品国产三级麻豆| 三上悠亚和黑人665番号| 成年女人免费播放视频| 天天干天天操天天扣| 亚洲一区二区三区五区| 黄页网视频在线免费观看| 国产一区成人在线观看视频| 521精品视频在线观看| 国产在线免费观看成人| 色呦呦视频在线观看视频| 日本免费午夜视频网站| 大陆胖女人与丈夫操b国语高清| 亚洲激情偷拍一区二区| 亚洲蜜臀av一区二区三区九色| 国产精品视频男人的天堂| 午夜精品亚洲精品五月色| 中文字幕第一页国产在线| 欧美日韩精品永久免费网址| 中国把吊插入阴蒂的视频| 免费观看污视频网站| 欧美色呦呦最新网址| 阿v天堂2014 一区亚洲| 偷拍自拍国产在线视频| 国产在线91观看免费观看| 亚洲自拍偷拍精品网| 亚洲成人av一区在线| 免费观看污视频网站| 天堂av狠狠操蜜桃| 亚洲变态另类色图天堂网| 中文字幕一区二区亚洲一区| 亚洲第一黄色在线观看| 日韩美女综合中文字幕pp| 综合一区二区三区蜜臀| 懂色av蜜桃a v| 沈阳熟妇28厘米大战黑人| 清纯美女在线观看国产| 深田咏美亚洲一区二区| 亚洲综合乱码一区二区| 欧美一区二区三区啪啪同性| 摧残蹂躏av一二三区| 在线观看911精品国产| 天天日天天天天天天天天天天| 经典亚洲伊人第一页| 久草电影免费在线观看| 人妻少妇精品久久久久久| 同居了嫂子在线播高清中文| 一级a看免费观看网站| 免费69视频在线看| 亚洲高清自偷揄拍自拍| 亚洲av极品精品在线观看| 成人免费公开视频无毒 | 天天干天天操天天插天天日| 免费高清自慰一区二区三区网站| 75国产综合在线视频| 国产精品福利小视频a| 岳太深了紧紧的中文字幕| 亚洲久久午夜av一区二区| 亚洲av日韩高清hd| 在线观看av观看av| 一级A一级a爰片免费免会员| 亚洲一区二区久久久人妻| 亚洲欧美综合在线探花| 99久久中文字幕一本人| 天天日天天添天天爽| 91欧美在线免费观看| 无码中文字幕波多野不卡| 日韩精品啪啪视频一道免费| 日本乱人一区二区三区| 精品久久久久久久久久中文蒉| 日韩美女综合中文字幕pp| 中文字幕日韩无敌亚洲精品| 2020韩国午夜女主播在线| 天天日天天添天天爽| 97色视频在线观看| 91自产国产精品视频| 91在线视频在线精品3| 欧美另类一区二区视频| 无码日韩人妻精品久久| 亚洲av黄色在线网站| 国产亚洲视频在线二区| 黄色黄色黄片78在线| 特大黑人巨大xxxx| 北条麻妃肉色丝袜视频| 婷婷激情四射在线观看视频| 黄色中文字幕在线播放| 在线观看欧美黄片一区二区三区| 国产使劲操在线播放| 91精品一区二区三区站长推荐| 在线免费91激情四射 | 亚洲专区激情在线观看视频| 91久久综合男人天堂| sspd152中文字幕在线| 亚洲欧美成人综合视频| 中文字幕高清免费在线人妻 | 91中文字幕免费在线观看| 亚洲综合在线视频可播放| 可以免费看的www视频你懂的| 精品亚洲在线免费观看| sejizz在线视频| 大鸡吧插逼逼视频免费看 | 久草福利电影在线观看| 91桃色成人网络在线观看| 久久午夜夜伦痒痒想咳嗽P| www天堂在线久久| 黄色视频成年人免费观看| 中文字幕日韩91人妻在线| 五十路熟女人妻一区二| avjpm亚洲伊人久久| 亚洲嫩模一区二区三区| 91免费观看在线网站| 视频一区 视频二区 视频| 密臀av一区在线观看| 天天色天天操天天透| 中文字幕人妻三级在线观看| 天天躁日日躁狠狠躁av麻豆| 大香蕉大香蕉大香蕉大香蕉大香蕉| 超碰公开大香蕉97| 人妻自拍视频中国大陆| 国产视频在线视频播放| 国产品国产三级国产普通话三级| 亚洲av在线观看尤物| 天天综合天天综合天天网| 东京热男人的av天堂| 日韩剧情片电影在线收看| 国产精品亚洲在线观看| 欧美日本国产自视大全| 一个色综合男人天堂| 人人爽亚洲av人人爽av| 亚洲国产免费av一区二区三区| AV无码一区二区三区不卡| 欧亚日韩一区二区三区观看视频| 久久艹在线观看视频| lutube在线成人免费看| 国产精品久久久黄网站| 搞黄色在线免费观看| 国产成人自拍视频播放 | 亚洲国产欧美一区二区三区…| 91色秘乱一区二区三区| 日本人竟这样玩学生妹| 男人和女人激情视频| 久精品人妻一区二区三区| 久久精品国产999| 一个人免费在线观看ww视频| 亚欧在线视频你懂的| 午夜精品福利一区二区三区p| 天天日天天干天天要| 亚洲av琪琪男人的天堂| 日韩精品激情在线观看| 亚洲图片偷拍自拍区| av天堂中文字幕最新| 国产av欧美精品高潮网站| 在线免费观看欧美小视频| 欧美黑人性猛交xxxxⅹooo| 97人妻无码AV碰碰视频| 任你操视频免费在线观看| 亚洲熟女综合色一区二区三区四区| 天码人妻一区二区三区在线看| 在线观看黄色成年人网站| 最近中文字幕国产在线| av高潮迭起在线观看| 成人高潮aa毛片免费| 粗大的内捧猛烈进出爽大牛汉子| 日本最新一二三区不卡在线 | 天天综合天天综合天天网| 中文字幕av第1页中文字幕| 亚洲成人黄色一区二区三区| 午夜久久久久久久99| 蜜桃臀av蜜桃臀av| 顶级尤物粉嫩小尤物网站| 美女福利视频网址导航| 亚洲丝袜老师诱惑在线观看| 91精品综合久久久久3d动漫| 国产变态另类在线观看| 福利视频广场一区二区| 欧美viboss性丰满| 超碰在线中文字幕一区二区| 亚洲一区二区三区在线高清| 男人在床上插女人视频| 青青青青在线视频免费观看| 日韩一个色综合导航| 老司机深夜免费福利视频在线观看| 在线新三级黄伊人网| 11久久久久久久久久久| 一区二区三区毛片国产一区| 在线观看免费岛国av| 成人免费公开视频无毒| 欧美亚洲国产成人免费在线| 青青青视频自偷自拍38碰| 日韩av有码一区二区三区4| 97精品成人一区二区三区| 超级av免费观看一区二区三区| 欧美日韩一级黄片免费观看| 天天摸天天干天天操科普| 18禁美女黄网站色大片下载| 天天干天天操天天扣| 99re国产在线精品| 性感美女高潮视频久久久| 黑人3p华裔熟女普通话| 日本成人不卡一区二区| 任你操任你干精品在线视频| 日本一区二区三区免费小视频| 天天日天天干天天舔天天射| brazzers欧熟精品系列| 欧美黑人与人妻精品| 国产精品久久久久久美女校花| 日韩人妻丝袜中文字幕| av在线免费资源站| 亚洲国产成人最新资源| 亚洲欧洲一区二区在线观看| 天天干天天操天天爽天天摸| 粉嫩av蜜乳av蜜臀| 偷拍自拍国产在线视频| 亚洲成高清a人片在线观看| 成年人黄色片免费网站| 亚洲推理片免费看网站| av无限看熟女人妻另类av| 成人国产激情自拍三区| av新中文天堂在线网址| 青青青青青青草国产| 热99re69精品8在线播放| 瑟瑟视频在线观看免费视频| 粉嫩av蜜乳av蜜臀| 最新的中文字幕 亚洲| 亚洲成人线上免费视频观看| 欧洲日韩亚洲一区二区三区| 色综合久久五月色婷婷综合| 少妇人妻久久久久视频黄片| 五十路息与子猛烈交尾视频 | 国产麻豆91在线视频| 国产视频精品资源网站| 久久人人做人人妻人人玩精品vr| 午夜精品一区二区三区4| 人妻自拍视频中国大陆| 深田咏美亚洲一区二区| 天天做天天干天天舔| 欧美精产国品一二三产品价格| 欧洲精品第一页欧洲精品亚洲| 亚洲青青操骚货在线视频| 中文字幕亚洲中文字幕| 久久一区二区三区人妻欧美| 中文字幕在线视频一区二区三区| 美女在线观看日本亚洲一区| 最新日韩av传媒在线| 国产又粗又硬又猛的毛片视频| 九色视频在线观看免费| 在线观看亚洲人成免费网址| 大鸡巴操b视频在线| 中文字幕在线一区精品| 91破解版永久免费| 日本熟妇色熟妇在线观看| 又色又爽又黄又刺激av网站| 91av精品视频在线| 特级无码毛片免费视频播放| 在线免费观看靠比视频的网站| 黄色中文字幕在线播放| 亚洲丝袜老师诱惑在线观看| 亚洲成人黄色一区二区三区| 精品久久久久久久久久中文蒉| 青草亚洲视频在线观看| 91精品国产91久久自产久强 | 成人av中文字幕一区| 中文字幕高清免费在线人妻| 亚洲码av无色中文| 亚洲天堂有码中文字幕视频 | 国产日韩一区二区在线看 | 久久久久久久亚洲午夜综合福利| 亚洲免费成人a v| 国产在线91观看免费观看| 欧美激情电影免费在线| 93精品视频在线观看| 姐姐的朋友2在线观看中文字幕| 午夜91一区二区三区| 日韩av中文在线免费观看| 欧美视频综合第一页| 香蕉av影视在线观看| 91精品国产91久久自产久强| 亚洲一级 片内射视正片| 鸡巴操逼一级黄色气| 午夜美女福利小视频| 一区二区三区欧美日韩高清播放| 大陆精品一区二区三区久久| 美女张开腿让男生操在线看| 女同性ⅹxx女同h偷拍| 亚洲男人让女人爽的视频| 1区2区3区4区视频在线观看| 老司机在线精品福利视频| 亚洲无线观看国产高清在线| 最近中文2019年在线看| 日本高清成人一区二区三区| 亚洲精品国产综合久久久久久久久 | 欧美激情电影免费在线| 国产精品大陆在线2019不卡| 夜女神免费福利视频| 天天干夜夜操啊啊啊| 果冻传媒av一区二区三区| 青青青激情在线观看视频| 中文字幕午夜免费福利视频| 特大黑人巨大xxxx| 中文字幕综合一区二区| 国产黑丝高跟鞋视频在线播放| 国产在线观看黄色视频| 东京干手机福利视频| 日本xx片在线观看| 天天射,天天操,天天说| 国产精品久久久久久久久福交| 青青青艹视频在线观看| 亚洲欧美国产综合777| 国产性生活中老年人视频网站| 亚洲一区二区三区五区| 一个色综合男人天堂| 91精品综合久久久久3d动漫| 在线观看免费岛国av| 中文字幕国产专区欧美激情| 欧美aa一级一区三区四区| 男女第一次视频在线观看| 手机看片福利盒子日韩在线播放| 91chinese在线视频| 精品美女在线观看视频在线观看| 久久久久久9999久久久久| 三级等保密码要求条款| 国产极品精品免费视频| 婷婷色国产黑丝少妇勾搭AV | 国产欧美精品一区二区高清| 无套猛戳丰满少妇人妻| 少妇深喉口爆吞精韩国| 都市家庭人妻激情自拍视频| 婷婷五月亚洲综合在线| 亚洲综合自拍视频一区| 国内精品在线播放第一页| 91香蕉成人app下载| 亚洲成人av一区久久| 91精品国产黑色丝袜| 成人动漫大肉棒插进去视频| 亚洲另类伦春色综合小| 适合午夜一个人看的视频| 日本在线一区二区不卡视频| 中国黄色av一级片| 国产一区二区视频观看| av大全在线播放免费| 在线成人日韩av电影| 在线观看免费视频色97| 日本熟妇喷水xxx| 国产麻豆乱子伦午夜视频观看| 亚洲欧美成人综合视频| 动漫精品视频在线观看| 国产视频一区在线观看| 五月天色婷婷在线观看视频免费| 国产精品视频欧美一区二区| 日本av熟女在线视频| 亚洲中文字幕国产日韩| www久久久久久久久久久| 精彩视频99免费在线| 天天操天天干天天日狠狠插| 亚洲另类伦春色综合小| 国产黄色a级三级三级三级| 国产精品黄片免费在线观看| 欧美日韩中文字幕欧美| 在线观看欧美黄片一区二区三区| 青草久久视频在线观看| 最新中文字幕乱码在线| 国产麻豆剧传媒精品国产av蜜桃| 91色秘乱一区二区三区| 少妇与子乱在线观看| 白嫩白嫩美女极品国产在线观看| 国产精品3p和黑人大战| 国产超码片内射在线| 11久久久久久久久久久| 亚洲欧美一卡二卡三卡| 九九视频在线精品播放| 熟女人妻一区二区精品视频| 免费一级黄色av网站| 天天草天天色天天干| xxx日本hd高清| 免费69视频在线看| 啊慢点鸡巴太大了啊舒服视频| 天天日天天干天天舔天天射| 亚洲乱码中文字幕在线| 国产在线91观看免费观看| 中国无遮挡白丝袜二区精品| sw137 中文字幕 在线| av成人在线观看一区| 国产自拍黄片在线观看| 国产精品一区二区久久久av| 色秀欧美视频第一页| 狠狠的往里顶撞h百合| 在线国产精品一区二区三区| 蜜臀成人av在线播放| 5528327男人天堂| 国产97在线视频观看| 夜夜骑夜夜操夜夜奸| 中文字幕日韩精品日本| 欧美成人猛片aaaaaaa| 欧美一区二区三区久久久aaa| 超碰97免费人妻麻豆| 美日韩在线视频免费看| 亚洲麻豆一区二区三区| 影音先锋女人av噜噜色| 99国产精品窥熟女精品| av天堂中文免费在线| 免费手机黄页网址大全| 好吊视频—区二区三区| 欧美爆乳肉感大码在线观看| 啊啊好大好爽啊啊操我啊啊视频| 天堂av在线最新版在线| 福利视频一区二区三区筱慧| 午夜毛片不卡在线看| 姐姐的朋友2在线观看中文字幕 | 好吊操视频这里只有精品| 亚洲欧美久久久久久久久| 亚洲中文字幕国产日韩| 中文字幕日本人妻中出| 黄色录像鸡巴插进去| 农村胖女人操逼视频| 91麻豆精品传媒国产黄色片| 国产美女午夜福利久久| 98视频精品在线观看| 亚洲免费视频欧洲免费视频| 人妻3p真实偷拍一二区| 天天操天天爽天天干| 班长撕开乳罩揉我胸好爽| 青青青艹视频在线观看| 天天艹天天干天天操| 亚洲av成人网在线观看| 午夜91一区二区三区| 激情人妻校园春色亚洲欧美| 爱有来生高清在线中文字幕| 人人妻人人爽人人添夜| 操操网操操伊剧情片中文字幕网| 丰满熟女午夜福利视频| 中文字幕亚洲久久久| 护士小嫩嫩又紧又爽20p| 人妻丝袜榨强中文字幕| 欧美老妇精品另类不卡片| 亚洲卡1卡2卡三卡四老狼| 亚洲天堂精品久久久| 中文字幕人妻三级在线观看| 在线播放国产黄色av| 国产福利小视频二区| 欧美亚洲中文字幕一区二区三区| 十八禁在线观看地址免费| 女生被男生插的视频网站| 非洲黑人一级特黄片| 人妻自拍视频中国大陆| 亚洲欧美日韩视频免费观看| 大学生A级毛片免费视频| 国产精品污污污久久| 99精品视频在线观看免费播放| 亚洲另类伦春色综合小| 国产精品sm调教视频| 亚洲一级av无码一级久久精品| 精品区一区二区三区四区人妻| 亚洲另类在线免费观看| 中文字幕—97超碰网| 中国黄色av一级片| 亚洲一级美女啪啪啪| 涩爱综合久久五月蜜臀| 国产91精品拍在线观看|