与1.7相比的重大变化
1、取消了segment数组,直接用table保存数据,锁的粒度更小,减少并发冲突的概率。
2、存储数据时采用了链表+红黑树的形式,纯链表的形式时间复杂度为O(n),红黑树则为O(logn),性能提升很大。什么时候链表转红黑树?当key值相等的元素形成的链表中元素个数超过8个的时候。
主要数据结构和关键变量
Node类存放实际的key和value值。
sizeCtl:
负数:表示进行初始化或者扩容,-1表示正在初始化,-N,表示有N-1个线程正在进行扩容
正数:0 表示还没有被初始化,>0的数,初始化或者是下一次进行扩容的阈值
TreeNode 用在红黑树,表示树的节点, TreeBin是实际放在table数组中的,代表了这个红黑树的根。// 内部类,和JDK1.7中static final class HashEntry<K,V>实现方式一样,仅仅改了名 static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; } final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); // key的哈希值再次散列,目的是让元素存放得更均匀 int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); // 如果table数组上这个元素为空,则直接吧新元素放到数组里 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) // 帮助扩容 tab = helpTransfer(tab, f); else { V oldVal = null; synchronized (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)))) { 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; } } } // 红黑树时插入数据 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) { /*判定成功后,由链表转为树,阈值为8,即链表长度大于等于8时,就将链表转为红黑树 由红黑树转链表的阈值为6*/ if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; } 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 // 循环CAS设置 SIZECTL 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") // 根据 SIZECTL初始化Table数组,如果SIZECTL<=0,则根据默认值16进行初始化 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; // 即取n的0.75作为后续扩容的阈值 sc = n - (n >>> 2); } } finally { sizeCtl = sc; } break; } } return tab; } public V get(Object key) { Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; int h = spread(key.hashCode()); if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) { // 检查table数组中这个元素是否就是需要获取的元素 if ((eh = e.hash) == h) { if ((ek = e.key) == key || (ek != null && key.equals(ek))) return e.val; } // 从树中寻找 else if (eh < 0) return (p = e.find(h, key)) != null ? p.val : null; // 从链表中寻找 while ((e = e.next) != null) { if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) return e.val; } } return null; }
ConcurrentHashMap_jdk1.8
最新推荐文章于 2024-10-16 06:45:00 发布
本文详细解析了Java并发HashMap的工作原理,包括其从1.7到1.8的主要变化,如取消segment数组、采用链表+红黑树形式存储数据、锁的粒度减小、链表转红黑树条件等。同时介绍了Node类、sizeCtl变量、TreeNode和TreeBin的作用,以及putVal和get方法的具体实现。

176

被折叠的 条评论
为什么被折叠?



