并发场景下HashMap.get导致cpu耗光

最近公司同事消息队列出现此种情况,以下分析

一、并发场景下HashMap.get导致cpu耗光

Java中HashMap.get导致cpu耗光bug,是由于HashMap.get里面的for循环出现了无限循环,出现死循环是因为map中的桶链表出现了循环链表,循环链表是因为并发put操作造成的,同时进行了resize();如果只有一个线程进行put操作,是不会出现循环链表,所以读写并发不会出现死循环,只有写写并发才有可能出现死循环。

/**

 * Transfers all entries from current table to newTable.

 */

void transfer(Entry[] newTable) {

Entry[] src = table;

    int newCapacity = newTable.length;

    for (int j = 0; j < src.length; j++) {

     Entry<K,V> e = src[j];

        if (e != null) {

            src[j] = null;

            do {

                Entry<K,V> next = e.next;

                int i = indexFor(e.hash, newCapacity);

                e.next = newTable[i];

                newTable[i] = e;

             e = next;

         } while (e != null);

      }

}

}

假设一个初始容量大小为2的HashMap(大多数人应该知道HashMap是采用数组的方式来存储key,value对象,基于对key的hashcode的hash来决定存储到数组的位置,冲突的情况下采用开放链表的方式来解决),目前里面有两个Entry,假设其存储在数组的同一位置,存储结构类似如下:
Entry[0] = A(.next) –> B(.next) –> null

假设同时有两个线程调用了put,同时触发resize。
第二个线程目前执行到Entry next = e.next这一行,此时其获取到的e为A,next为B。

第一个线程已执行完transfer动作,但还没执行完resize的剩余动作,如A和B在容量扩大为4时hash到的位置仍然相同,那么此时transfer中的newTable的结构变成:
Entry[0] = B(.next) –> A(.next) –> null

此时第二个线程继续执行,进入这段代码:

do {

    // 此时e为A,e.next为B,此行相当于next = B;

    Entry<K,V> next = e.next;  

    // 假设i=0

    int i = indexFor(e.hash, newCapacity); 

    // newTable[0]为null,此行相当于A.next = null;

    e.next = newTable[i]; 

    // 此行相当于newTable[0] = A;

    newTable[i] = e; 

    // 此行相当于e = B;

    e = next;

} while (e != null);

此时newTable的结构为:
Entry[0] = A(.next) –> null

e不为null,继续执行:

do {

    // 此时e为B,e.next为A(第一个线程修改的),此行相当于next = A;

    Entry<K,V> next = e.next;  

    // 假设i = 0

    int i = indexFor(e.hash, newCapacity); 

    // newTable[0]为A,此行相当于B.next = A;

    e.next = newTable[i]; 

    // 此行相当于newTable[0] = B;

    newTable[i] = e; 

    // 此行相当于e = A;

    e = next;

} while (e != null);

此时newTable的结构为:
Entry[0] = B(.next) –> A(.next) –> null

e不为null,继续执行:

do {

    // 此时e为A,A.next为null,此行相当于next = null

    Entry<K,V> next = e.next;  

    // 假设i = 0

    int i = indexFor(e.hash, newCapacity); 

    // newTable[0]为B,此行相当于A.next = B;

    e.next = newTable[i]; 

    // 此行相当于newTable[0] = A;

    newTable[i] = e; 

    // 此行相当于e = null;

    e = next;

} while (e != null);

此时newTable的结构为:
Entry[0] = A(.next) <--> B(.next)
无限循环就此造成,此时如果有线程要执行HashMap.get(A) ,就会直接进入无限循环,耗光CPU。

从上面的分析可以看出,要导致HashMap.get出现无限循环,还真有些不容易,至少要有两个线程在同时进行put,同时触发resize,并且之前一个桶里的两个相邻的对象在容量变了后仍然hash到了同样的数组位置中,这样才有可能导致问题,因此这现象只偶尔出现(不过我们以前曾经悲催的出现过一个集群重启,全部碰到了Velocity的那个错误使用HashMap的bug,导致严重故障)

并发那点事

1、list线程安全类CopyOnWriteArrayList

java.util.concurrent.CopyOnWriteArrayList<E>

Add/Set

public boolean add(E e) {

final ReentrantLock lock = this.lock;

lock.lock();

try {

    Object[] elements = getArray();//获取当前数组

    int len = elements.length;

    Object[] newElements = Arrays.copyOf(elements, len + 1);//数组拷贝

    newElements[len] = e;

    setArray(newElements);

    return true;

} finally {

    lock.unlock();

}

}

遍历

public Iterator<E> iterator() {

//返回一个new对象,类内部参数数组为当前数组,即如果当前list数组出现新增、删除

而遍历时不会出现同步异常,但会有脏数据出现 ex: CopyOnWriteArrayListTest

return new COWIterator<E>(getArray(), 0);

}

<think>好的,用户问的是关于这段代码的用途:“maxValue > hashMap.get(key) ? maxValue : hashMap.get(key);”。首先,我需要确认用户的问题是什么。看起来他们可能想了解这段代码的作用,或者如何正确使用它。 首先,这段代码是一个三元运算符,比较maxValue和hashMap.get(key)的值,然后返回较大的那个。这实际上是用来更新maxValue为当前最大值。不过,这里可能存在一些问题,比如如果hashMap.get(key)返回null,就会抛出NullPointerException。用户可能在使用时遇到了这个问题,或者想确认代码的正确性。 接下来,我需要考虑用户可能的背景。他们可能刚开始学习Java,对集合和三元运算符不太熟悉。之前的对话中,用户询问了getOrDefault方法,现在这段代码涉及HashMapget方法,可能他们正在处理统计或查找最大值的场景,比如统计频率然后找最大值。 用户的问题可能是在尝试找出HashMap中的最大值,但代码写法有误。原代码中的逗号使用错误,正确的应该是返回两个值中的一个。另外,没有处理null的情况,如果key不存在,get会返回null,导致异常。这时候,可能需要使用之前提到的getOrDefault方法来避免null,比如设置默认值为0或其他合理值。 另外,用户可能没有意识到这段代码并不能直接更新maxValue,需要将结果赋值给maxValue。正确的写法应该是maxValue = Math.max(maxValue, hashMap.getOrDefault(key, 0)); 这样既避免了null,又简化了代码。 还需要考虑用户的应用场景,比如统计词频或元素频率,然后找出最大的那个。这种情况下,正确的做法是遍历HashMap的entrySet,或者使用Stream API的max方法。用户可能没有意识到更高效的方法,或者想优化现有代码。 最后,用户可能希望得到这段代码的修正版本,以及更优的替代方案。因此,在回答时需要指出原代码的问题,提供修正后的代码,并推荐使用Math.max方法或Java 8的Stream API来提升代码的可读性和健壮性。</think>你提供的代码片段 `maxValue > hashMap.get(key) ? maxValue, hashMap.get(key);` 存在语法和逻辑问题。推测你的目标是**从 `HashMap` 中获取某个键对应的值,并与 `maxValue` 比较,取较大值**。以下是修正后的解析和实现方法: --- ### **问题分析** 1. **语法错误** - 三元运算符格式应为:`条件 ? 值1 : 值2`(你误用了逗号 `,`)。 - 缺少赋值操作(结果未存储到变量中)。 2. **潜在风险** - 若 `key` 不存在于 `HashMap`,`hashMap.get(key)` 返回 `null`,直接比较会抛出 `NullPointerException`。 --- ### **修正后的代码** #### 方法 1:使用 `getOrDefault` 避免空指针 ```java // 假设 hashMap 的值类型为 Integer,默认值设为 0 int currentValue = hashMap.getOrDefault(key, 0); maxValue = (maxValue > currentValue) ? maxValue : currentValue; ``` #### 方法 2:直接使用 `Math.max` 简化 ```java maxValue = Math.max(maxValue, hashMap.getOrDefault(key, 0)); ``` --- ### **应用场景示例** 假设需要统计单词频率并记录最大值: ```java Map<String, Integer> wordCountMap = new HashMap<>(); wordCountMap.put("apple", 3); wordCountMap.put("banana", 5); int maxFrequency = 0; // 遍历所有键值对,更新最大值 for (String word : wordCountMap.keySet()) { maxFrequency = Math.max(maxFrequency, wordCountMap.get(word)); } System.out.println("最高频率: " + maxFrequency); // 输出 5 ``` --- ### **优化建议** 1. **直接遍历值集合** 无需通过 `keySet()` 遍历键再取值,可直接遍历 `values()`: ```java for (int value : hashMap.values()) { maxValue = Math.max(maxValue, value); } ``` 2. **使用 Java 8 Stream API** 一行代码直接获取最大值: ```java int maxValue = hashMap.values().stream().max(Integer::compare).orElse(0); ``` --- ### **关键点总结** - 使用 `getOrDefault` 或 `containsKey` 避免空指针。 - 优先使用 `Math.max()` 替代三元运算符,提升可读性。 - 遍历 `values()` 或利用 Stream API 更高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值