Java8 ConcurrentHashMap(一) 源码解析

     目录

1、定义

2、Node / CounterCell / ForwardingNode / ReservationNode 

3、TreeNode 

4、TreeBin 

5、构造方法

6、put / putIfAbsent / putAll

7、treeifyBin / untreeify


     ConcurrentHashMap表示一个线程安全的,基于分段锁实现的Map,其底层数据结构跟HashMap是一致的,都是数组+链表或者红黑树,即数组元素是链表头或者红黑树的根节点,分段锁就是对这里的数组元素加锁,而且是直接使用synchronized关键字加锁,本篇博客就详细分析该类的实现细节。

1、定义

     ConcurrentHashMap的类继承关系如下:

ConcurrentMap继承自Map,新增的接口如下:

 

 后面会详细介绍相关方法的用途和实现的。该类包含的实例属性如下:

    //保存键值对的数组
    transient volatile Node<K,V>[] table;
    
    //扩容时使用的,保存扩容后的新的Node数组
    private transient volatile Node<K,V>[] nextTable;
    
    //如果table未初始化,sizeCtl记录了初始容量,如果正在初始化则为-1,如果正在扩容则为非-1的一个负值
    //如果已经初始化或者扩容完成,则记录了一个阈值,超过该值会触发扩容
    private transient volatile int sizeCtl;
    
    //记录尚未分配出去的待转移的数组元素的个数,初始值是扩容前的数组容量,然后逐步减少至0
    private transient volatile int transferIndex;

     //baseCount和CounterCell两个都是用来记录Map中键值对的个数
    //如果cas修改baseCount失败,就会修改当前对应的CounterCell的value
    //获取键值对个数时,将两者累加起来
    private transient volatile long baseCount;
    //创建或者扩容CounterCells时的锁
    private transient volatile int cellsBusy;
    //每个线程会根据当前线程probe值计算所属的CounterCell,一般高并发下一个线程对应一个CounterCell
    private transient volatile CounterCell[] counterCells;

    //执行遍历时的视图
    private transient KeySetView<K,V> keySet;
    private transient ValuesView<K,V> values;
    private transient EntrySetView<K,V> entrySet;

 该类包含了多个静态常量,如下:

    //最大容量
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    //默认初始容量
    private static final int DEFAULT_CAPACITY = 16;

    //数组的最大容量
    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;
    
    //链表中元素个数超过此值,就需要将其转化成一个红黑树,前提是数组长度大于MIN_TREEIFY_CAPACITY
    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;
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    
    //特殊的hash值,正常的hash值都是大于0
    static final int MOVED     = -1; //ForwardingNode使用的hash值
    static final int TREEBIN   = -2; //红黑树根节点的hash值
    static final int RESERVED  = -3; // hash for transient reservations
    //用来计算key的hash值,保证算出来的hash值大于0
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

    //获取CPU的个数
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /** 序列化使用的*/
    private static final ObjectStreamField[] serialPersistentFields = {
        new ObjectStreamField("segments", Segment[].class),
        new ObjectStreamField("segmentMask", Integer.TYPE),
        new ObjectStreamField("segmentShift", Integer.TYPE)
    };

  除此之外,还有多个表示属性偏移量的静态字段,通过static代码块初始化,如下:

其中ASHIFT表示一个数组元素占的字节数取2的对数,64位下对象数组一个数组元素8个字节,ASHIFT的值是3,ABASE表示数组本身用于基于元素类型、长度等信息占用的字节数,根据这两个可以计算数组某个索引对应的数组元素的偏移量。 

2、Node / CounterCell / ForwardingNode / ReservationNode 

     Node和CounterCell都是两个内部类,前者表示Map中的一个键值对,后者用来计数的,两者的定义如下:

 //Contended注解可避免两个CounterCell实例在同一个高速缓存行中导致伪共享的问题
 @sun.misc.Contended static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash; //key的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;
        }

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null && //key不为null
                    (v = e.getValue()) != null &&  //value不为null
                    (k == key || k.equals(key)) && //两者key一致
                    (v == (u = val) || v.equals(u))); //两者val一致
        }

        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
               //从当前节点往后遍历,找到键值都相等的节点
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }

   ForwardingNode和ReservationNode都继承自Node,前者表示一个因为扩容而正在移动中的节点,后者表示一个空节点,加锁时使用,其定义如下:

static final class ForwardingNode<K,V> extends Node<K,V> {
        final Node<K,V>[] nextTable;
        ForwardingNode(Node<K,V>[] tab) {
            //hash值是MOVED
            super(MOVED, null, null, null);
            this.nextTable = tab;
        }

      Node<K,V> find(int h, Object k) {
            //注意此时遍历的是扩容后的新数组nextTable,如果数组元素是ForwardingNode表示原来的数组元素中的节点全部转移到新数组中去了,可以在新数组中查找
            outer: for (Node<K,V>[] tab = nextTable;;) {
                Node<K,V> e; int n;
                if (k == null || tab == null || (n = tab.length) == 0 ||
                    (e = tabAt(tab, (n - 1) & h)) == null) //如果对应索引的数组元素为空
                    return null;
                //e为根据h计算出来的数组索引的元素    
                for (;;) {
                    int eh; K ek;
                    //如果hash值和key都匹配则认为匹配成功
                    if ((eh = e.hash) == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    if (eh < 0) { //正常节点的hash值都是大于0的
                        if (e instanceof ForwardingNode) {
                            //如果e是ForwardingNode,则更新查找的tab,外层的for循环继续,e会更新
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            continue outer;
                        }
                        else
                            //其他的特殊节点,在e所在的链表中查找
                            return e.find(h, k);
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值