ConcurrentHashMap源码解析

    HashMap是我们很常用的一个集合类。但在并发编程中使用HashMap可能会导致程序死循环(1.8不会),而使用HashTable效率又非常低下,基于以上两个原因,便有了ConcurrentHashMap的登场机会。

    温馨提示:源代码来自JDK1.8

一、ConcurrentHashMap的常量

//最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认容量
private static final int DEFAULT_CAPACITY = 16;
//最大可能需要的数组大小,用于toCharArray等相关方法
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//默认并发数
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//负载因子
private static final float LOAD_FACTOR = 0.75f;
//哈希冲突的链表转换为红黑树的阈值
static final int TREEIFY_THRESHOLD = 8;
//哈希冲突的红黑树转换为链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
//
static final int MIN_TREEIFY_CAPACITY = 64;
//
private static final int MIN_TRANSFER_STRIDE = 16;
//
private static int RESIZE_STAMP_BITS = 16;
//帮助Map扩容的最大线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
//记录size大小的偏移量
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//节点哈希字段的编码
static final int MOVED     = -1; // hash for forwarding nodes
static final int TREEBIN   = -2; // hash for roots of trees
static final int RESERVED  = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
//
static final int NCPU = Runtime.getRuntime().availableProcessors();

二、ConcurrentHashMap中的属性变量

//存放键值对的哈希表
transient volatile Node<K,V>[] table;
//扩容时生成的哈希表,容量时原容量的2private transient volatile Node<K,V>[] nextTable;
//
private transient volatile long baseCount;
//控制标识符
private transient volatile int sizeCtl;
//
private transient volatile int transferIndex;
//
private transient volatile int cellsBusy;
//
private transient volatile CounterCell[] counterCells;

// 视图
private transient KeySetView<K,V> keySet;
private transient ValuesView<K,V> values;
private transient EntrySetView<K,V> entrySet;

    其中sizeCtl在不同的地方有不同的用途,其值也不同,所代表的的含义也不同

    1、负数代表正在进行初始化或扩容操作

    2、-1代表初始化

    3、-N表示有N-1个线程正在进行扩容操作

    4、整数或0代表hash表还没有初始化,这个数值表示初始化或下一次进行扩容的阈值

三、哈希节点

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;

    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }
    /*代码省略大法*/
}

    注意value和next使用volatie修饰。

四、主要方法

    get方法:

public V get(Object key) {
    //哈希表
    Node<K,V>[] tab;
    Node<K,V> e, p;
    int n, eh;
    K ek;
    //计算keyhash    int h = spread(key.hashCode());
    //如果哈希表不为空,哈希表长度大于0,并且根据key计算的hash值能找到对应的节点
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
        //如果这个节点的hash值等于keyhash        if ((eh = e.hash) == h) {
            //如果这个节点的key和传入的key相等,则返回该值
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        //去红黑树中找
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        //遍历链表,如果找到hash值和key值和传入的key值和计算出的哈希值相同则返回这个节点的值
        while ((e = e.next) != null) {
            if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

    大致步骤为:

    1、计算传入key的hash值
    2、根据hash值使用hash算法映射相应的哈希桶
    3、若桶无节点,返回null
    4、如果桶有节点,若节点的key值和hash值与传入的key值和计算出的hash值相同,则返回节点的value
    5、若当前节点时树节点,则去红黑树中查找

    6、若有链表,则遍历链表查找

put方法:

public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    //如果keyvalue为空,抛出异常
    if (key == null || value == null) throw new NullPointerException();
    //计算hash    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //如果哈希表为空,初始化哈希表
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //通过hash算法计算出要插入的桶,若桶为空,则使用CAS操作将这个键值对放入桶中
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                    new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        //如果这个节点的hash值是-1,则说明Map正在进行扩容操作,当前线程协助扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        //出现hash冲突或者要更新值
        else {
            V oldVal = null;
            //加锁
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //如果传入的key和计算出的hash值与该节点相同,则覆盖旧值返回
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                //覆盖旧值
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            //说明出现了hash冲突
                            if ((e = e.next) == null) {
                                //将新节点插入到链表的尾部
                                pred.next = new Node<K,V>(hash, key,
                                        value, null);
                                break;
                            }
                            //寻找链表的下一个节点
                        }
                    }
                    //按照红黑树的方式处理插入节点
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                //判断链表长度是否大于转换红黑树的阈值
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                //将旧值返回
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

    put方法的大致步骤为:

    1、判断key值和value值,任何一个为空都抛出异常,也就是ConcurrentHashMap不能存入null值

    2、计算key的hash值

    3、判断哈希表是否为空,为空则初始化,不为空进行下一步

    4、通过hash算法计算出要插入的桶,若桶为空,则使用CAS操作将这个键值对放入桶中,桶不为空进行下一步

    5、如果这个节点的hash值是-1,则说明Map正在进行扩容操作,当前线程协助扩容

    6、进行到这里需要加锁,接下来继续判断,遍历链表

    7、如果传入的key和计算出的hash值与该节点相同,则覆盖旧值返回,如果不相同则出现了hash冲突,将新节点插入链表尾部

    8、如果这个节点是树节点,则按照红黑树的方式去操作

    9、在最后需要判断链表长度是否超过转换为红黑树的阈值,若超过则转换,不然返回旧值(正常插入旧值为null)

initTable方法

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        //说明有其他线程正在初始化哈希表
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // lost initialization race; just spin
        //当前线程获取了初始化哈希表的权利,使用CAS操作将SIZECTL设置为-1,表示当前线程在初始化哈希表
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    //初始化容量
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    //初始化哈希表
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    //下次扩容的大小  相当于0.75*n
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

    这个方法不难,我觉得看我的注释应该就能理解了。

remove方法

public V remove(Object key) {
    return replaceNode(key, null, null);
}

final V replaceNode(Object key, V value, Object cv) {
    //计算hash    int hash = spread(key.hashCode());
    //遍历哈希表
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //如果哈希表为空,或者根据key计算出对应的哈希桶内没有元素就返回
        if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
            break;
        //Map正在扩容,帮助扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            boolean validated = false;
            //加锁
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    //如果是Node节点
                    if (fh >= 0) {
                        validated = true;
                        for (Node<K,V> e = f, pred = null;;) {
                            K ek;
                            //如果传入的key和计算出的hash值与该节点相同
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                //得到旧值
                                V ev = e.val;
                                //如果cv为空,说明是移除操作,cv等于ev,说明是替换操作
                                if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                    oldVal = ev;
                                    //节点值替换
                                    if (value != null)
                                        e.val = value;
                                    //链表删除节点
                                    else if (pred != null)
                                        pred.next = e.next;
                                    //单节点删除节点
                                    else
                                        setTabAt(tab, i, e.next);
                                }
                                break;
                            }
                            //继续遍历链表
                            pred = e;
                            if ((e = e.next) == null)
                                break;
                        }
                    }
                    //TreeBin节点,按照TreeBin的操作去完成
                    else if (f instanceof TreeBin) {
                        validated = true;
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> r, p;
                        if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                            V pv = p.val;
                            if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                oldVal = pv;
                                if (value != null)
                                    p.val = value;
                                else if (t.removeTreeNode(p))
                                    setTabAt(tab, i, untreeify(t.first));
                            }
                        }
                    }
                }
            }
            if (validated) {
                if (oldVal != null) {
                    if (value == null)
                        addCount(-1L, -1);
                    return oldVal;
                }
                break;
            }
        }
    }
    return null;
}

remove方法使用replaceNode方法实现,传入两个null值

    remove方法的步骤大致是:

    1、计算key的hash值

    2、死循环遍历哈希表

    3、如果哈希表为空,或者根据key计算出对应的哈希桶内没有元素就返回

    4、如果Map正在扩容,则当前线程协助扩容

    5、如果3,4都不满足,这个时候需要加锁后操作

    6、如果是Node节点,遍历链表,如果节点的key值和hash值与传入的key值和计算出的hash值相同,进行下一步操作

    7、因为这是remove方法,所以将这个节点的value置null,然后删除这个节点(单节点置为null,多节点则使用链表删除)

    8、若是树节点,则按照红黑树的方式去操作

    9、最后返回旧值(没有找到返回null)

扩容方法我有点看不懂,等我看懂之后再来补上

猜你喜欢

转载自blog.csdn.net/yanghan1222/article/details/80259228