从上述来看,ConcurrentHashMap的底层数据组织为数组+链表。依据Jdk8后的HashMap,可以推测,在对应条件下,链表会转为红黑树结构。事实也是如此,请看下代码。
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; } // 此处省略其内部方法,感兴趣的,可以自行查看 }ConcurrentHashMap,与HashMap一样,其内部也有专门为红黑树服务的TreeNode。
所以,从数据组织方面来看,其实ConcurrentHashMap与同版本的HashMap,可以说就是一个模子刻出来的(毕竟都是Doug Lea带着撸的)。
两者的区别,或者说ConcurrentHashMap的精妙之处,就在于ConcurrentHashMap对多线程的考虑与处理。
其中的细节挺多的,我只阐述我对其中一些大头的理解(因为很多细节,我也不知道,也是看了大佬的总结,才发现)。
数据处理方式 put 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) { // 数据校验,如果key或value为Null,直接NPE if (key == null || value == null) throw new NullPointerException(); // 通过spread方法,计算hash值(本质还是与HashMap一样,针对hashCode进行高低16位异或计算等) int hash = spread(key.hashCode()); // 记录链表长度 int binCount = 0; // 这里的循环操作是为了之后的CAS操作(就是CAS的自旋操作) for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) // 同HashMap一样,如果数组为空或长度为0,则进行数组初始化操作(循环头中已经完成赋值操作) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 如果数组对应位置为null,则通过CAS操作,进行值的插入操作 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } // 如果对应节点的Node.hash值为MOVED=-1 else if ((fh = f.hash) == MOVED) // 进行resize协助操作(具体协助方式,还没研究) tab = helpTransfer(tab, f); else { V oldVal = null; synchronized (f) { if (tabAt(tab, i) == f) { // 如果数组对应位置(即首节点)的哈希值大于等于零(树化后等情况下,对应位置哈希值小于零) // static final int MOVED = -1; // hash for forwarding nodes // static final int TREEBIN = -2; // hash for roots of trees // static final int RESERVED = -3; // hash for transient reservations if (fh >= 0) { // 说明此情况下,数组对应位置,存储的是链表。进行链表插入,遍历操作(具体参照HashMap的put操作) binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { 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; } } } // 如果数组对应位置的元素,是树化节点(即为TreeBin实例) else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; // 调用putTreeVal方法,进行红黑树的值插入操作 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; // 判断onlylfAbsent参数,进行val设置。具体参照HashMap的put方法的对应位置解释 if (!onlyIfAbsent) p.val = value; } } } } // 前面的各类操作,都会计算binCount(数组当前位置存储的节点数) if (binCount != 0) { // 如果对应节点数超过了树化阈值TREEIFY_THRESHOLD=8 if (binCount >= TREEIFY_THRESHOLD) // 对数组当前位置,进行树化操作 treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } // 计数 addCount(1L, binCount); return null; } 小结ConcurrentHashMap的魅力在于其线程安全的实现,有机会好好研究研究,专门写一个相关的博客。
三,总结其实,Java集合主要从两个维度分析。一个是底层数据组织方式,如链表与数组(基本就这两种,或者如HashMap那样组合两种)。另一个是线程安全方式,就是线程安全与非线程安全。
最后就是由于一些底层数据组织方式的调整,带来的循环,有序等特性。