基本属性

常量

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;
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;


/*
 * Encodings for Node hash fields. See above for explanation.
 */
hash值是-1,表示这是一个forwardNode节点
static final int MOVED     = -1; // hash for forwarding nodes
hash值是-2  表示这时一个TreeBin节点
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();

成员变量

// Node元素数组,大小是2的整数次幂
transient volatile Node<K,V>[] table;

private transient volatile Node<K,V>[] nextTable;
private transient volatile long baseCount;
//hash表初始化或扩容时的一个控制位标识
//负数代表正在进行初始化或扩容操作
//-1代表正在初始化, N 表示有N-1个线程正在进行扩容操作
//正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小
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;

与HashMap一样,concurrentHashMap内部存储结构也是Node<K,V>,存储结构相似,但有一些差别

  1. val 和 next 属性设置了volatile
  2. 不允许调用setValue方法直接改变Node的value
  3. 增加了find方法辅助map.get()方法
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;
        }

       ...
}

当链表长度过长的时候,会转换为TreeNode。但是与HashMap不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。而且TreeNode在ConcurrentHashMap集成自Node类,而并非HashMap中的集成自LinkedHashMap.Entry<K,V>类,也就是说TreeNode带有next指针,这样做的目的是方便基于TreeBin的访问。

put 方法

final V putVal(K key, V value, boolean onlyIfAbsent) {
    //不允许存放 null
    if (key == null || value == null) throw new NullPointerException();
    // hash散列算法:使高16位参与运算,并保证hash值为正数
    int hash = spread(key.hashCode());
    int binCount = 0;
    // 死循环
    for (Node<K,V>[] tab = table;;) {
        // f: 当前key对应的桶元素
        // n: 当前table的大小
        // i: 当前key 应该放在table的位置索引
        // fh:当前key对应桶元素的hash值
        Node<K,V> f; int n, i, fh;
        // 如果table没有初始化
        if (tab == null || (n = tab.length) == 0)
            // 初始化hash表,也是懒加载
            tab = initTable();
        // 通过路由算法找到当前值,应该存放的位置i
        // 如果当前位置没有值
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //通过cas 将当前位置由null 变为 node对象
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        // 如果当前位置有元素,且hash值为 -1
        else if ((fh = f.hash) == MOVED)
            // 帮助扩容
            tab = helpTransfer(tab, f);
        // 出现hash冲突,需要将数据挂在链表上,或红黑树中
        else {
            // 老的val
            V oldVal = null;
            // 锁当前桶中已存在的对象 f,只锁了自己的桶
            synchronized (f) {
                // 拿到i索引位置的数据 和 f是不是同一个
                if (tabAt(tab, i) == f) {
                    // 当前桶下不是树,可能是链表,或者是空
                    if (fh >= 0) {
                        // 计数,记录链表长度
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 判断二者的值是否是一样
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                // 值一样,说明不是hash冲突,而是同一个value
                                oldVal = e.val;
                                if (!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;
                            }
                        }
                    }
                    // 当前f已经树化
                    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;
}

是否可以存null

在单线程环境中,不会存在一个线程操作该 HashMap 时,其他的线程将该 HashMap 修改的情况,可以通过 contains(key)来做判断是否存在这个键值对,从而做相应的处理;
而在多线程环境下,可能会存在多个线程同时修改键值对的情况,这时是无法通过contains(key)来判断键值对是否存在的,这会带来一个二义性的问题(无法判断是值本身为null还是说集合中就没这个值),Doug Lea说二义性是多线程中不能容忍的!

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
        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;
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

treeifyBin方法

private final void treeifyBin(Node<K,V>[] tab, int index) {
    //b: 
    //n: 当前table数组长度
    //sc:
    Node<K,V> b; int n, sc;
    if (tab != null) {
        // 如果table数组长度小于 64,尝试扩容
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            // 尝试扩容
            tryPresize(n << 1);
        // 转红黑树
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            synchronized (b) {
                if (tabAt(tab, index) == b) {
                    TreeNode<K,V> hd = null, tl = null;
                    for (Node<K,V> e = b; e != null; e = e.next) {
                        TreeNode<K,V> p =
                            new TreeNode<K,V>(e.hash, e.key, e.val,
                                              null, null);
                        if ((p.prev = tl) == null)
                            hd = p;
                        else
                            tl.next = p;
                        tl = p;
                    }
                    setTabAt(tab, index, new TreeBin<K,V>(hd));
                }
            }
        }
    }
}

tryPresize方法

private final void tryPresize(int size) {
    //c: 计算扩容数组长度
    int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
        tableSizeFor(size + (size >>> 1) + 1);
    //sc: sizeCtl
    int sc;
    while ((sc = sizeCtl) >= 0) {
        // sc =0 代表还没有初始化,sc > 0 代表下次扩容的大小
        Node<K,V>[] tab = table; int n;
        // 没有初始化
        if (tab == null || (n = tab.length) == 0) {
            // 取二者的最大值
            n = (sc > c) ? sc : c;
            // 通过cas 设置sizeCtl为-1, 标识正在初始化
            if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    // 防止ABA的问题
                    if (table == tab) {
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
            }
        }
        // 扩容长度已经小于扩容阈值(已经扩容完成)  或者 超过最大容量,不扩容 
        else if (c <= sc || n >= MAXIMUM_CAPACITY)
            break;
        // 已经初始化,开始扩容
        else if (tab == table) {
            // 获取扩容戳(高16位做扩容标识,低16位做扩容线程数(2代表1个线程在扩容))
            int rs = resizeStamp(n);
            if (sc < 0) {
                // 已经开始扩容了,帮助扩容
                Node<K,V>[] nt;
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            // 暂时没有线程扩容,我先设置sizeCtl标记,设置为2,代表一个线程扩容
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
        }
    }
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐