ConcurrentHashMap源码解析(上)

1. 前言

HashMap不是线程安全的,在处理并发的时候可能会出现问题。而HashTable虽然是线程安全的,但是却是需要所有操作竞争同一把锁,效率自然会下降很多。而如果容器中有多把锁,每一把锁锁一段数据,这样在多线程访问不同段的数据时,就不会出现锁竞争了,进而可以有效提高并发效率。这就是ConcurrentHashMap采用的分段锁思想。

2. 简介

ConcurrentHashMapHashMap的线程安全版本,内部也是采用数组+链表+红黑树存储元素的,因为要尽可能保持并发的效率,所以代码会更复杂,成员变量也有很多不同,这些都是为了并发的效率考虑的,ConcurrentHashMap的并发效率比简单粗暴HashTable会高很多。

3. 成员变量

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

// 默认表容量(16), 注:表的容量必须是2的次幂
private static final int DEFAULT_CAPACITY = 16;
    
// 最大可能的数组大小,toArray和相关方法需要
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;

// 树化阈值,当表中一个单元格处的链表长度达到8的时候,就具备了树化的条件之一
static final int TREEIFY_THRESHOLD = 8;

// 反树化阈值,当表中一个单元格处的链表长度小于6时,会将红黑树转化为链表
static final int UNTREEIFY_THRESHOLD = 6;

// 这个记录的是树化的条件之一,当表的长度64,就具备了树化的条件之一
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=-1,表示当前结点是FWD(forwarding)结点(该处结点已经被迁移)
static final int MOVED     = -1; // hash for forwarding nodes

// 当node的hash=-2,表示当前结点已经树化,且当前结点是TreeBin结点(TreeBin结点代理操作红黑树)
static final int TREEBIN   = -2; // hash for roots of trees

// node的hash=-3,临时保留的hash值,暂时用不到
static final int RESERVED  = -3; // hash for transient reservations

// 0x7fffffff转化为2进制就是1111111111111111111111111111111(31个1)
// 在计算哈希值时会用到
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

// 当前系统的cpu数量
static final int NCPU = Runtime.getRuntime().availableProcessors();

// 用来存储表中的数据,ConcurrentHashMap底层就是Node数组
transient volatile Node<K,V>[] table;

// 在数组扩容时需要用到的表,表示新表,扩容之后将新表赋给table,nextTable重新设置为null
private transient volatile Node<K,V>[] nextTable;

// 表示列表中的元素数量
private transient volatile long baseCount;

// -1 :当前有线程正在初始化table数组
// -N :高16位表示扩容的版本号,低16位表示扩容线程数(1 + n threads),n表示当前参与扩容的线程数
// =0 :在调用无参构造方法创建ConcurrentHashMap对象时,不会给sizeCtl赋值,默认为0,这样第一次初始化时表的容量为16
// >0 :
//	如果table未初始化,表示初始化时表的容量大小
//	如果table已经初始化,表示下一次扩容时表应该达到的阈值	
private transient volatile int sizeCtl;


private transient volatile int transferIndex;

// 表示counterCells数组当前有无线程加锁
private transient volatile int cellsBusy;

// 可以对比LongAdder为了降低线程冲突创建的cells数组
// 因为baseCount表示表中元素数量,当有多个线程通知修改
// baseCount时,只会有一个线程会成功,冲突率太高,通过
// 创建数组分散热点,可以很好地减小冲突发生的概率,时间换空间
// 我写过一篇LongAdder的解析,大家可以去看一看啊~
private transient volatile CounterCell[] counterCells;

HashMap相比,ConcurrentHashMap使用sizeCtl来控制扩容时需要达到的阈值

  • -1 :当前有线程正在初始化table数组

  • -N :高16位表示扩容的版本号,低16位表示扩容线程数(1 + n threads),n表示当前参与扩容的线程数

  • =0 :在调用无参构造方法创建ConcurrentHashMap对象时,不会给sizeCtl赋值,默认为0,这样第一次初始化时表的容量为16

  • **>**0 :

    • 如果table未初始化,表示初始化时表的容量大小
    • 如果table已经初始化,表示下一次扩容时表应该达到的阈值

4. 构造方法

// 无参的构造方法,sizeCtl未赋值,默认为0,如果调用这个构造方法创建对象,则第一次扩容时table的容量会是16
public ConcurrentHashMap() {
    
}
// 传入一个指定容量,虽然传入了指定容量,但实际上初始化时的容量是最小的大于等于这个数的2的次幂
public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   // 下面这个方法会计算最小的大于等于initialCapacity*1.5+1的2的次幂
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    	// 设置sizeCtl为计算出的cap,表示新建数组时需要new的数组的大小
        this.sizeCtl = cap;
    }

public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
    	// 设置sizeCtl为默认容量DEFAULT_CAPACITY(16)
        this.sizeCtl = DEFAULT_CAPACITY;
    	// 调用这个方法将传的Map集合插入列表中
        putAll(m);
    }

public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    	// 调用另一个构造方法
        this(initialCapacity, loadFactor, 1);
    }
		
// 指定初始容量,负载因子,和并发级别
// 虽然指定了初始容量,但是真正初始化时的容量确是最小的大于等于1+指定初始容量/负载因子的2的次幂
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {
    	// 负载因子小于等于0或初始容量小于0或并发级别小于0,直接抛出异常
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
    	// 如果初始容量小于并发级别,则设置初始容量为并发级别
        if (initialCapacity < concurrencyLevel)   // Use at least as many bins
            initialCapacity = concurrencyLevel;   // as estimated threads
    	// 计算1+初始容量/负载因子的大小
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            				  // 计算最小的大于等于1+初始容量/负载因子的2的次幂
            MAXIMUM_CAPACITY : tableSizeFor((int)size);
    	// 设置sizeCtl为计算出的cap,表示创建数组时需要new的数组的大小
        this.sizeCtl = cap;
    }

5. 内部类

Node结点

这个节点类型就是我们存储到ConcurrentHashMap中的基本类型

// Node结点
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结点

        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 &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        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结点

这个结点在扩容的时候会用到。

// ForwardingNode结点
// 如果是一个写的线程(并发扩容),则需要为创建新表贡献一份力
// 如果是一个读的线程,则调用内部类的find(int h, Object k)方法读出数据
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;
    }
	// 到新表上读数据
    Node<K,V> find(int h, Object k) {
        // loop to avoid arbitrarily deep recursion on forwarding nodes
        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;
            for (;;) {
                int eh; K ek;
                if ((eh = e.hash) == h &&
                    ((ek = e.key) == k || (ek != null && k.equals(ek))))
                    return e;
                if (eh < 0) {
                    if (e instanceof ForwardingNode) {
                        tab = ((ForwardingNode<K,V>)e).nextTable;
                        continue outer;
                    }
                    else
                        return e.find(h, k);
                }
                if ((e = e.next) == null)
                    return null;
            }
        }
    }
}

TreeNode结点

表示红黑树上的结点。

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;    // needed to unlink next upon deletion
    
    // 颜色
    boolean red;

    TreeNode(int hash, K key, V val, Node<K,V> next,
             TreeNode<K,V> parent) {
        super(hash, key, val, next);
        this.parent = parent;
    }

    Node<K,V> find(int h, Object k) {
        return findTreeNode(h, k, null);
    }

    /**
     * Returns the TreeNode (or null if not found) for the given key
     * starting at given root.
     */
    final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
        if (k != null) {
            TreeNode<K,V> p = this;
            do  {
                int ph, dir; K pk; TreeNode<K,V> q;
                TreeNode<K,V> pl = p.left, pr = p.right;
                if ((ph = p.hash) > h)
                    p = pl;
                else if (ph < h)
                    p = pr;
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                    return p;
                else if (pl == null)
                    p = pr;
                else if (pr == null)
                    p = pl;
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                else if ((q = pr.findTreeNode(h, k, kc)) != null)
                    return q;
                else
                    p = pl;
            } while (p != null);
        }
        return null;
    }
}

TreeBin结点

代为管理红黑树

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;
        // values for 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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值