map 的问题.
一个线程在删除,一个线程在增加,map在扩容的时候,复制一份,复制的这份里面可能包含这个公司.导致remove的时候没有移除掉.
那么改为 currentHashMap 就可以了.
使用Java的同学应该都是知道HashMap是线程不安全的,不能够并发的去put和get,如果有并发操作,会抛出ConcurrentModificationException这个异常。不过可能很多同学没有注意,这个异常并不是一定会抛出的。而并发的去对HashMap对象进行put和get的结果,是可能造成死循环。
年前在线上,也遇到了这么一次。当时是有同学找过来,说有台机器的load很高,已经启动了流量控制,也没有请求进到容器中。后来dump了thread,也通过top -H看到占用CPU比较高的线程,发现都是在执行HashMap.get,想到可能就是上面的原因,后来看了源码,发现确实是这么一个问题。
具体对于HashMap在并发操作时陷入死循环的分析,可以参看这篇博客
http://pt.alibaba-inc.com/wp/dev_related_969/hashmap-result-in-improper-use-cpu-100-of-the-problem-investigated.html
死循环理解:
map扩容的时候,比如两个节点 E1,E2 本来是E2的next 是E1.
在两个线程情况下,第一个线程跑完了扩容,导致key冲排序E1指向E2.此时第二个线程 刚将E2的next指向E1拿到 (数据已经改变)
当第二个线程运行到E2的时候,还是使用老的排序,认为E2指向E1 导致死循环.
---------------------并发环境下,HashMap不能准确获取值--------------------------
大家都知道HashMap不是线程安全的,但是大家的理解可能都不是十分准确。很显然读写同一个key会导致不一致大家都能理解,但是如果读写一个不变的对象会有问题么?看看下面的代码就明白了。
1 import java.util.HashMap;
2 import java.util.Map;
3 import java.util.Random;
4 import java.util.concurrent.ExecutorService;
5 import java.util.concurrent.Executors;
6 import java.util.concurrent.TimeUnit;
7 import java.util.concurrent.atomic.AtomicInteger;
8
9 public class HashMapTest2 {
10 static void doit() throws Exception{
11 final int count = 200;
12 final AtomicInteger checkNum = new AtomicInteger(0);
13 ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(100);
14 //
15 final Map<Long, String> map = new HashMap<Long, String>();
16 map.put(0L, "www.imxylz.cn");
17 //map.put(1L, "www.imxylz.cn");
18 for (int j = 0; j < count; j++) {
19 newFixedThreadPool.submit(new Runnable() {
20 public void run() {
21 map.put(System.nanoTime()+new Random().nextLong(), "www.imxylz.cn");
22 String obj = map.get(0L);
23 if (obj == null) {
24 checkNum.incrementAndGet();
25 }
26 }
27 });
28 }
29 newFixedThreadPool.awaitTermination(1, TimeUnit.SECONDS);
30 newFixedThreadPool.shutdown();
31
32 System.out.println(checkNum.get());
33 }
34
35 public static void main(String[] args) throws Exception{
36 for(int i=0;i<10;i++) {
37 doit();
38 Thread.sleep(500L);
39 }
40 }
41 }
42
结果一定会输出0么?结果却不一定。比如某一次的结果是:
0
3
0
0
0
0
9
0
9
0
查看了源码,其实出现这个问题是因为HashMap在扩容是导致了重新进行hash计算。
在HashMap中,有下面的源码:
2 if (key == null)
3 return getForNullKey();
4 int hash = hash(key.hashCode());
5 for (Entry<K,V> e = table[indexFor(hash, table.length)];
6 e != null;
7 e = e.next) {
8 Object k;
9 if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
10 return e.value;
11 }
12 return null;
13 }
在indexOf中就会导致计算有偏移。
1 static int indexFor(int h, int length) {
2 return h & (length-1);
3 }
很显然在Map的容量(table.length,数组的大小)有变化时就会导致此处计算偏移变化。这样每次读的时候就不一定能获取到目标索引了。为了证明此猜想,我们改造下,变成以下的代码。
final Map<String, String> map = new HashMap<String, String>(10000);
执行多次结果总是输出:
0
0
0
0
0
0
0
0
0
0
当然了如果只是读,没有写肯定没有并发的问题了。改换Hashtable或者ConcurrentHashMap肯定也是没有问题了。
相关推荐
4. 避免在多线程环境中直接使用HashMap:如果你确定不需要在多线程环境下共享HashMap,那么可以考虑局部变量的方式,只在单个线程中使用HashMap,这样就无需担心线程安全问题。 总结起来,理解HashMap的线程不安全...
这样可以避免直接操作原HashMap导致的并发问题,但这种方法会增加内存开销。 4. **使用CopyOnWriteArrayList/CopyOnWriteArraySet**:对于需要遍历并修改的情况,可以考虑使用这些类,它们在修改时会创建一个新的...
在Java的HashMap中,多线程并发环境下的扩容操作可能会引发死锁问题。这主要发生在JDK 1.7版本,因为其扩容机制采用了头插法。以下详细解释这个问题及其解决方案。 首先,HashMap的扩容机制是在容量达到阈值时触发...
5. HashMap的并发问题如何解决? 答:在多线程环境下,可以使用ConcurrentHashMap,它是线程安全的HashMap实现。 五、HashMap与HashSet的关系 HashSet基于HashMap实现,每个元素作为HashMap的一个键,值为null。...
5. **HashMap并发问题**: - 在多线程环境下,HashMap的put、remove等操作可能导致数据不一致,甚至死循环。使用ConcurrentHashMap来保证线程安全。 6. **每隔一秒输出一句话**: - 可以使用...
**JVM内存模型**、**触发垃圾回收**、**HashMap并发问题**:JVM内存分为堆、栈等,使用jmap dump堆内存,System.gc()触发GC,多写者单读HashMap可能导致数据不一致。 42. **git rebase**:用于合并分支,将分支的...
TBB 开源库及并发 Hashmap 的使用 TBB 开源库是 Intel 公司开发的开源并行计算库,用于在多核平台上进行并行化程序的开发。该库使用标准 C++ 编写,提供了一个灵活的框架来实现数据并行计算。使用 TBB 可以使用户...
总的来说,马士兵老师的HashMap学习笔记不仅涵盖了HashMap的基础知识,还深入探讨了其在不同JDK版本中的实现细节,特别是并发环境下的问题和解决方案。理解这些内容对于提升Java编程技能和优化代码性能具有重要价值...
4. **并发问题**:HashMap不是线程安全的,这意味着在多线程环境中,同时对HashMap进行读写操作可能会导致数据不一致。如果需要线程安全的哈希表,可以使用`ConcurrentHashMap`。 5. **null值**:HashMap允许键和值...
如果多个线程并发地访问并修改`HashMap`,则可能导致数据不一致性问题。为了在多线程环境中安全地使用`HashMap`,开发者需要自己负责同步,例如使用`Collections.synchronizedMap(new HashMap,V>())`创建线程安全的`...
这里假设数据加载过程可能较为耗时,因此需要同步以避免并发问题。 #### 缓存更新与垃圾回收 在实际应用中,HashMap缓存可能会面临两个关键问题:缓存更新和垃圾回收。由于HashMap不会自动清除过期或不再使用的...
- 注意HashMap的并发问题,如果在多线程环境下,使用Collections.synchronizedMap()或使用ConcurrentHashMap来保证线程安全。 总结起来,HashMap和HashSet是Java集合框架的重要组成部分,它们利用哈希技术提供了...
因此,在多线程环境中使用`HashMap`时,如果不采取额外的同步措施,可能会导致数据不一致或其他并发问题。 #### 2. 同步机制 - **Hashtable**: 使用内部同步机制来确保线程安全,这意味着在执行关键操作时会锁定...
- **HashMap**: 相对于`HashTable`,`HashMap`在单线程或低并发情况下具有更好的性能。这是因为`HashMap`没有同步开销,因此操作速度更快。 #### 四、内部实现细节 - **HashTable**: 内部使用了`Entry`类来表示...
- **HashTable**:由于其同步策略,不适用于高并发场景。 9. **迭代器**: - **HashMap**:提供的迭代器是非同步的。 - **HashTable`**:提供的迭代器是同步的。 #### 三、示例代码对比 以下是一些示例代码,...
对于并发场景,可以使用CopyOnWriteArrayList(线程安全的ArrayList变体)和ConcurrentHashMap(线程安全的HashMap变体)。 总之,ArrayList和HashMap是Java集合框架中的重要组件,理解它们的工作原理和适用场景,...
总结来说,深入理解ArrayList、LinkedList、HashMap和HashSet的源码,有助于我们更好地利用它们的特性,优化代码性能,并在面临并发问题时做出正确的选择。对于开发人员来说,掌握这些基础数据结构的实现原理是提高...
6. **并发问题** - HashMap不是线程安全的。在多线程环境下,如果不进行同步控制,可能会出现并发修改异常。在需要线程安全的场景下,可以使用`ConcurrentHashMap`。 以上是HashMap在Java 8中的核心知识点。通过...
然而,由于其非线程安全的特性,对于多线程环境,通常需要考虑使用`ConcurrentHashMap`来保证并发访问的安全性。此外,还需注意内存占用,因为HashMap会存储键值对的引用,如果键或值是大对象,可能会导致内存消耗过...