ConcurrentHashMap和HashMap在各自的构造函数中都没有做数组初始化,初始化放在了第一次添加元素中。值得注意的是ConcurrentHashMap中有一个属性sizeCtl特别重要,理清晰它的变革,也就理解了整个Map源码的流程。下面是它的说明
/** * Table initialization and resizing control. When negative, the * table is being initialized or resized: -1 for initialization, * else -(1 + the number of active resizing threads). Otherwise, * when table is null, holds the initial table size to use upon * creation, or 0 for default. After initialization, holds the * next element count value upon which to resize the table. *
* 控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义 *
* 1. 当为负数时:-1代表正在初始化,-N代表有N-1个线程正在举行扩容 *
* 2.当为0时:代表当时的table还没有被初始化 *
* 3.当为正数时:未初始化表示的是初始化数组的初始容量,假如已经初始化, * 记录的是扩容的阈值(达到阈值举行扩容) */ private transient volatile int sizeCtl;再看一下ConcurrentHashMap带初始化容量的代码
/** * Creates a new, empty map with an initial table size * accommodating the specified number of elements without the need * to dynamically resize. * * @param initialCapacity The implementation performs internal * sizing to accommodate this many elements. * @throws IllegalArgumentException if the initial capacity of * elements is negative * * 此时sizeCtl记录的就是数组的初始化容量 * * 比如initialCapacity=5 * 调用tableSizeFor(5+5/2+1)==tableSizeFor(8) */ public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException(); int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); this.sizeCtl = cap; } /** * Returns a power of two table size for the given desired capacity. * See Hackers Delight, sec 3.2 * 返回一个大于即是c的2的幂次方数 * * 当c=8时 * n = c-1=7 * 接下来验算最闭幕果 * 0000 0000 0000 0000 0000 0000 0000 0111 * >>> 1 * = 0000 0000 0000 0000 0000 0000 0000 0011 * | 0000 0000 0000 0000 0000 0000 0000 0111 * = 0000 0000 0000 0000 0000 0000 0000 0111 * >>> 2 * = 0000 0000 0000 0000 0000 0000 0000 0001 * | 0000 0000 0000 0000 0000 0000 0000 0111 * = 0000 0000 0000 0000 0000 0000 0000 0111 * >>> 4 * = 0000 0000 0000 0000 0000 0000 0000 0000 * | 0000 0000 0000 0000 0000 0000 0000 0111 * = 0000 0000 0000 0000 0000 0000 0000 0111 * 下面再 >>> 8 和 >>> 16后的二进制都是0 * 所以最闭幕果就是111,也就是7最后返回结果再+1,即是8 * * 总结 右移一共1+2+4+8+16=31位,和与之对应 | 运算 * 最终把n的二进制中全部1移到低位。新的数高位都是0,低位都是1。这样格式的数在HashMap中提到过,就是2的幂次-1。 * 最后结果是这个数+1,那就是2的幂次。 */ private static final int tableSizeFor(int c) { int n = c - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }当我们new ConcurrentHashMap(c) 时,初始化容量并不是c,而是一个大于即是c的2的幂次方数。我们利用发射来验证下
public static void main(String[] args) { ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap(5); Class clazz = concurrentHashMap.getClass(); try { Field field = clazz.getDeclaredField("sizeCtl"); //打开私有访问 field.setAccessible(true); //获取属性 String name = field.getName(); //获取属性值 Object value = field.get(concurrentHashMap); System.out.println("ConcurrentHashMap的初始容量为:= "+value); } catch (Exception e) { e.printStackTrace(); } }--打印结果是: Map的初始容量=8put & putVal
/** * Maps the specified key to the specified value in this table. * Neither the key nor the value can be null. * *
The value can be retrieved by calling the {@code get} method * with a key that is equal to the original key. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with {@code key}, or * {@code null} if there was no mapping for {@code key} * @throws NullPointerException if the specified key or value is null */ @Override public V put(K key, V value) { return putVal(key, value, false); } /** * Implementation for put and putIfAbsent * */ final V putVal(K key, V value, boolean onlyIfAbsent) { //假如有空值大概空键,直接抛非常 if (key == null || value == null) { throw new NullPointerException(); } //两次hash,减少hash冲突,可以均匀分布 int hash = spread(key.hashCode()); int binCount = 0; //迭代当前table for (Node[] tab = table; ; ) { Node f; int n, i, fh; //1. 假如table未初始化,先初始化 if (tab == null || (n = tab.length) == 0) { tab = initTable(); } //假如i位置没有数据,cas插入 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //cas和外侧else if条件形成双保险,保证数据安全 if (casTabAt(tab, i, null, new Node(hash, key, value, null))) { break; // no lock when adding to empty bin } } //2. hash值是MOVED表示数组正在扩容,则协助扩容,先扩容在新加元素 else if ((fh = f.hash) == MOVED) { tab = helpTransfer(tab, f); } else { //hash计算的bucket不为空,且当前没有处于扩容操作,举行元素添加 V oldVal = null; //对当前bucket举行加锁,保证线程安全,执行元素添加操作 synchronized (f) { //判断是否为f,防止它变成tree if (tabAt(tab, i) == f) { //hash值>=0 表示该节点是链表结构 if (fh >= 0) { binCount = 1; //e记录的是头节点 for (Node e = f; ; ++binCount) { K ek; //相同的key举行put就会覆盖原先的value if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node pred = e; if ((e = e.next) == null) { //插入链表尾部 pred.next = new Node(hash, key, value, null); break; } } } else if (f instanceof TreeBin) { Node p; binCount = 2; //红黑树结构旋转插入 if ((p = ((TreeBin) f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) { p.val = value; } } } } } if (binCount != 0) { //链表长度大于8时转换红黑树 if (binCount >= TREEIFY_THRESHOLD) { treeifyBin(tab, i); } if (oldVal != null) { return oldVal; } break; } } } //统计size,而且查抄是否需要扩容 addCount(1L, binCount); return null; }putVal()总体是自旋+CAS的方式,流程和HashMap一样。