[JDK源码]-J.U.C-ConcurrentHashMap

Posted L._l

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[JDK源码]-J.U.C-ConcurrentHashMap相关的知识,希望对你有一定的参考价值。

由于作者水平有限,如有什么错误点,多谢指出。

ConcurrentHashMap

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable 
    //保存K-V 节点
	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 转为 TreeNode 节点,继承了Node节点
     static final class TreeNode<K,V> extends Node<K,V> 
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    
        boolean red;
     
    //链表Node转为红黑树,放在 数组位置上的头节点,本身不保存数据
    static final class TreeBin<K,V> extends Node<K,V> 
        TreeNode<K,V> root;
        volatile TreeNode<K,V> first;
        volatile Thread waiter;	//等待线程
        volatile int lockState;	//锁状态
        static final int WRITER = 1; // 写锁
        static final int WAITER = 2; // 等待写锁
        static final int READER = 4; // 读锁
    
    //保存数据的数组 2的倍数
     transient volatile Node<K,V>[] table;

    /*扩容时候生成的下一个数组   */
    private transient volatile Node<K,V>[] nextTable;

    /*计数器     */
    private transient volatile long baseCount;

    /*用于控制hash表的初始化和扩容,-1:hash初始化,-1+参与扩容操作的线程数:用于扩容操作,正数:hash表的容量   */
    private transient volatile int sizeCtl;

    /*在扩容时,多线程竞争转移 槽 区间时使用     */
    private transient volatile int transferIndex;

    /*在扩容时自旋锁使用    */
    private transient volatile int cellsBusy;

    /* CounterCell hash表,如果不为空,那么肯定是2的倍数   */
    private transient volatile CounterCell[] counterCells;

    // 通过迭代器遍历时候 的 几种视图
    private transient KeySetView<K,V> keySet;
    private transient ValuesView<K,V> values;
    private transient EntrySetView<K,V> entrySet;  

    static final int MOVED     = -1; // resize时 forwarding 节点 的hash值
    static final int TREEBIN   = -2; // 红黑树根节点的hash值
    static final int RESERVED  = -3; // 创建 reservationNode 节点的hash值
    static final int HASH_BITS = 0x7fffffff; // 正常hash用的位数
   static final class ReservationNode<K,V> extends Node<K,V> 
        ReservationNode() 
            super(RESERVED, null, null, null);
        
    
	//创建初始容量的
    public ConcurrentHashMap(int initialCapacity) 
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        //最大容量  MAXIMUM_CAPACITY = 1<<30 
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        //此时保存 hash表的最大容量
        this.sizeCtl = cap;
    
    //转移操作时,如果slot没有节点可以转移或已经转移成功,将在相应的slot放上这个类的对象
    static final class ForwardingNode<K,V> extends Node<K,V> 
        final Node<K,V>[] nextTable;
        ForwardingNode(Node<K,V>[] tab) 
            super(MOVED, null, null, null);
            this.nextTable = tab;
        
    
    //减少hash冲突,异或的结果继续和HASH_BITS 与运算
    static final int spread(int h) 
        return (h ^ (h >>> 16)) & HASH_BITS;//0x7fffffff
    

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) throw new NullPointerException(); //不允许放入空值
    int hash = spread(key.hashCode());	//计算 hash
    int binCount = 0; //记录元素个数
    for (Node<K,V>[] tab = table;;) 	//循环直到 插入成功
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)//为空 初始化 hash表
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) 
        //根据hash值找到应该保存的数组的位置,如果没放入,通过CAS,封装成 Node对象放入索引位 i的位置
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                  
        
        //这里,f就是找到下标位i的node元素,这时根据MOVED标志看看是数组正在扩容还是数据迁移,如果迁移,那么调用helpTransfer方法帮助完成迁移
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else 
            V oldVal = null;
            synchronized (f) 	//对f上锁
                if (tabAt(tab, i) == f) 	//索引下标 i 处节点没有被更改
                    if (fh >= 0) 	//链表
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) //遍历链表
                            K ek;
                            //当前与 要放入的 hash 相同
                            if (e.hash == hash &&
                                ((ek = e.key) == key || //比较 地址
                                 //比较 equals 
                                 (ek != null && key.equals(ek)))) 
                                oldVal = e.val;	//保存找到的旧值
                                if (!onlyIfAbsent)//如果没有设置 onlyIfAbsent ,覆盖旧值
                                    e.val = value;
                                break;
                            
                            //遍历到结尾 说明没有当前节点
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) 
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            
                        
                    //红黑树操作
                    else if (f instanceof TreeBin) 
                        //红黑树,在 slot中放置了一个 TreeBin对象 ,然后才是根节点 所以直接设置2
                        Node<K,V> p;
                        binCount = 2;
               //插入红黑树,如果返回值不为空,说明红黑树包含了K,根据onlyIfAbsent 来决定是否覆盖原来的值
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) 
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        
                    
                
            
            if (binCount != 0) 
                //节点超过 TREEIFY_THRESHOLD = 8
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);//转换为红黑树
                if (oldVal != null)//如果发生了冲突,替换原来的值,则返回
                    return oldVal;
                break;
            
        
    
    addCount(1L, binCount);//增加一个节点计数,看看是否需要扩容
    return 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(); 
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) 
            try //双重判断,避免CAS成功,但后面释放了sc后 又有线程进入
                if ((tab = table) == null || tab.length == 0) 
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//默认16
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];//创建数组
                    table = tab = nt;
                    //sc保存最大的数量,这个n是 0.75
                    sc = n - (n >>> 2);
                
             finally 
                sizeCtl = sc;
            
            break;
        
    
    return tab;

addCount

private final void addCount(long x, int check) 
    CounterCell[] as; long b, s;
    if ((as = counterCells) != null ||//已经创建
        //直接在baseCount 变量上计数。如果失败进入初始化 as,
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) 
        CounterCell a; long v; int m;
        boolean uncontended = true;
        //初始化as
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) 
            fullAddCount(x, uncontended);//创建as或者添加计数器
            return;
        
        if (check <= 1)//如果check<= 1 直接退出
            return;
        s = sumCount();//获取当前hash表的数据量
    
    if (check >= 0) //检查表的大小,要不要扩容
        Node<K,V>[] tab, nt; int n, sc;
        //s是当前数据量,大于等于 当前容量  && 数组不为空 && 数组小于最大容量
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) 
            int rs = resizeStamp(n);	//计算扩容时 使用的stamp
            if (sc < 0)  //如果sc小于0,表明已经开始扩容
                //stamp 已经改变  || 达到最大帮助resize的线程数  	MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
                //nextTable 为空,扩容完毕 
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                //增加帮助 resize 的线程数
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt); //如果成功 开始扩容
            
            //将 sc设置为  (rs << RESIZE_STAMP_SHIFT) + 2)
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);//开始扩容
            s = sumCount(); //重新计算 数据量
        
    

transfer扩容

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) 
    int n = tab.length, stride;
    //计算每个线程负责的区间,单核就单线程
    //否则(n >>> 3) / NCPU。   最小值MIN_TRANSFER_STRIDE = 16
    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<

以上是关于[JDK源码]-J.U.C-ConcurrentHashMap的主要内容,如果未能解决你的问题,请参考以下文章

JDK1.8源码下载及idea2021导入jdk1.8源码

关于JDK源码:我想聊聊如何更高效地阅读

关于JDK源码:我想聊聊如何更高效地阅读

idea 导入 jdk源码 解决compile code 后阅读jdk 源码

关于JDK源码:我想聊聊如何更高效地阅读.md

怎样读jdk源码