jdk8中是如何解决jdk7中的HashMap死循环的?
jdk8中是如何解决jdk7中的HashMap死循环的?
HashMap死循环问题,作为考核一个袁同学的知识功底和经验水平,大家津津乐道!对于没有真实遇到过这种问题的同学,可能也没有去研究过这种问题的同学,真是害苦了!
具体是啥问题呢?
我简单描述下: 某同学将HashMap()作为一个共享变量,供所有线程使用,然后还乐呵呵地进行put/get/remove..., 然后后来他的cpu被打满了,业务响应不了了,请排查问题发生的原因?
jdk1.7 中才有哦!
如下例子,反正我是没有模拟出死循环出来!
public class HashMapDeadLoopTest extends Thread { private static Map<Integer, Integer> sharedMap = new HashMap<>(2); private static AtomicInteger keyCounter = new AtomicInteger(1); private static LoopPointerObjectDump table[] = new LoopPointerObjectDump[3]; private static int size = 0; public static void main(String[] args) throws InterruptedException, IOException { // 开400个线程同时跑(过小的线程数不易模拟出结果),死循环命中率很高,但是机器可能被卡死,小心运行 int i, runNum = 400; HashMapDeadLoopTest[] tests = new HashMapDeadLoopTest[runNum]; for( i = 0; i < runNum; i++) { tests[i] = new HashMapDeadLoopTest(); tests[i].start(); } // 自己模拟单个线程出现死循环情况,说明 // HashMapDeadLoopTest test = new HashMapDeadLoopTest(); // LoopPointerObjectDump next1 = new LoopPointerObjectDump(); // LoopPointerObjectDump next2 = new LoopPointerObjectDump(next1); // // test.put(next2); // LoopPointerObjectDump[] newTable = new LoopPointerObjectDump[3]; // newTable[0] = next2; // test.transfer(newTable, true); System.in.read(); } void put(LoopPointerObjectDump e) { table[size++] = e; } void transfer(LoopPointerObjectDump[] newTable, boolean rehash) { int newCapacity = newTable.length; int loopId = 0; for (LoopPointerObjectDump e : table) { while(null != e) { LoopPointerObjectDump next = e.next; // 假设线程一看不到线程最后的这个变更,next已经被赋旧值,打开注释慢慢查看死循环效果 // if(++loopId == 1) { // next = e; // } // if(loopId > 100000) { // break; // } int i = 0; e.next = newTable[i]; newTable[i] = e; e = next; } System.out.println("loopId:" + loopId); } } @Override public void run() { while (keyCounter.get() < 1000000) { sharedMap.put(keyCounter.get(), keyCounter.get()); keyCounter.addAndGet(2); System.out.println(Thread.currentThread().getName() + " : put a-> " + keyCounter.get()); } } private static class LoopPointerObjectDump { private LoopPointerObjectDump next; public LoopPointerObjectDump() { next = null; } public LoopPointerObjectDump(LoopPointerObjectDump next) { this.next = next; } public void setNext(LoopPointerObjectDump next) { this.next = next; } } } 不管怎么样,原理咱们还是来理解下的! // java.utils.HashMap.put() public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; // 新元素,插入 addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { // 容量达到阀值,扩容 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; // 转移老容器内的元素到新容器内,重新平衡分布元素 transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { // 注意,只要达到 e != null 条件,死循环就生成了 while(null != e) { // 获取末尾元素 Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); // 此处可能形成循环链 select * from com.test.HashMapDeadLoopTest, 查看 循环链 e.next = newTable[i]; newTable[i] = e; e = next; } } } jdk8 中的 HashMap, 是这样的! public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 容量为空时重新赋值 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 元素不存在,则直接插入数组即可,线程安全性? if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; // 原值已存在,直接替换 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 如果是 LinkedHashMap 实现的话,会使用红黑树作为数据结构,调用其 putTreeVal() else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { // 找到最后一个 next 不会 null 的位置,插入元素 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 如果树的深度大于阀值-1, 则重新调整,平衡二叉树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // 找到元素存在,直接进入后续更新 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 当元素存在时,更新,并返回旧值 if (e != null) { // existing mapping for key V oldValue = e.value; // 存在才添加判定 if (!onlyIfAbsent || oldValue == null) e.value = value; // LinkedHashMap 预留 afterNodeAccess(e); return oldValue; } } // 修改+1 ++modCount; // 容量超过阀值,扩容 if (++size > threshold) resize(); // LinkedHashMap 预留 afterNodeInsertion(evict); return null; } /** * Replaces all linked nodes in bin at index for given hash unless * table is too small, in which case resizes instead. */ final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; // 对于小于容量 64 的情况,直接扩容解决 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { // 否则将数据结构升级为 TreeNode TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { // 二叉树建立 tl -> p -> p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) // 如果结构发生变更,则做一次平衡操作 hd.treeify(tab); } } /** * Forms tree of the nodes linked from this node. * @return root of tree */ final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null; for (TreeNode<K,V> x = this, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; x.left = x.right = null; if (root == null) { x.parent = null; x.red = false; root = x; } else { K k = x.key; int h = x.hash; Class<?> kc = null; for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; // 根据hash值决定 node 放在左子树还有右子树 if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; // 找到叶子节点为空的位置,插入 x , 并做平衡操作 if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; root = balanceInsertion(root, x); break; } } } } moveRootToFront(tab, root); } // 平衡二叉树的节点插入 static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) { x.red = true; for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { if ((xp = x.parent) == null) { x.red = false; return x; } else if (!xp.red || (xpp = xp.parent) == null) return root; if (xp == (xppl = xpp.left)) { if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; xp.red = false; xpp.red = true; x = xpp; } else { if (x == xp.right) { root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateRight(root, xpp); } } } } else { if (xppl != null && xppl.red) { xppl.red = false; xp.red = false; xpp.red = true; x = xpp; } else { if (x == xp.left) { root = rotateRight(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateLeft(root, xpp); } } } } } } /** * Ensures that the given root is the first node of its bin. */ static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) { int n; if (root != null && tab != null && (n = tab.length) > 0) { int index = (n - 1) & root.hash; TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; if (root != first) { Node<K,V> rn; tab[index] = root; TreeNode<K,V> rp = root.prev; if ((rn = root.next) != null) ((TreeNode<K,V>)rn).prev = rp; if (rp != null) rp.next = rn; if (first != null) first.prev = root; root.next = first; root.prev = null; } assert checkInvariants(root); } } // 扩容 /** * Initializes or doubles table size. If null, allocates in * accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or move * with a power of two offset in the new table. * * @return the table */ final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { // 超过最大容量限制后,不再扩容,而是直接返回原值 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 判定扩容(*2)后不超过最大限制,且原有容量需大于最小容量,后更新扩容阀值,否则走后续逻辑扩容 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
不要害怕今日的苦,你要相信明天,更苦!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?