实例:
package com.bijian.study.hashmap; import java.util.HashMap; public class TestLock { private HashMap map = new HashMap(); public TestLock() { Thread t1 = new Thread() { public void run() { for (int i = 0; i < 50000; i++) { map.put(new Integer(i), i); } System.out.println("t1 over"); } }; Thread t2 = new Thread() { public void run() { for (int i = 0; i < 50000; i++) { map.put(new Integer(i), i); } System.out.println("t2 over"); } }; Thread t3 = new Thread() { public void run() { for (int i = 0; i < 50000; i++) { map.put(new Integer(i), i); } System.out.println("t3 over"); } }; Thread t4 = new Thread() { public void run() { for (int i = 0; i < 50000; i++) { map.put(new Integer(i), i); } System.out.println("t4 over"); } }; Thread t5 = new Thread() { public void run() { for (int i = 0; i < 50000; i++) { map.put(new Integer(i), i); } System.out.println("t5 over"); } }; t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } public static void main(String[] args) { new TestLock(); } }
运行结果:
1. 报如下错误
t5 over t4 over Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 21604 at java.util.HashMap.addEntry(Unknown Source) at java.util.HashMap.put(Unknown Source) at com.bijian.study.hashmap.TestLock$1.run(TestLock.java:13) t3 over t2 over
2.这个程序会hang住不运行了,而且CPU会占用100%
下面我们对第二种错误进行分析
JDK自带工具分析
1. jconsole.exe(此工具未发现能分析Dump Thread的功能)
点击运行
选中相应的进程,点“连接”即可进入Java监视和管理控制台,如下所示:
点击“线程”页签,查看线程情况
2. jps、jstack <pid>
a. 打cmd窗口,将JDK下的jps.exe拖入回车运行,获得相应线程的PID
这里TestLock的PID是5048
b. 然后在cmd中输入jstack 5048,回车,就可看到Dump thread信息
3. jvisualvm.exe
点击运行JDK下的jvisualvm.exe。
启动后,点击“线程Dump(T)”选项,即可看到Dump thread信息
Dump thread会看到,程序hang到:
"DestroyJavaVM" prio=6 tid=0x011ffc00 nid=0x11e0 waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "Thread-3" prio=6 tid=0x03f46c00 nid=0x1520 runnable [0x041cf000] java.lang.Thread.State: RUNNABLE at java.util.HashMap.transfer(Unknown Source) at java.util.HashMap.resize(Unknown Source) at java.util.HashMap.addEntry(Unknown Source) at java.util.HashMap.put(Unknown Source) at com.bijian.study.hashmap.TestLock$4.run(TestLock.java:42) "Low Memory Detector" daemon prio=6 tid=0x01a93800 nid=0xac runnable [0x00000000] java.lang.Thread.State: RUNNABLE
CPU利用率过高一般是因为出现了出现了死循环,导致部分线程一直运行,占用cpu时间。问题原因就是HashMap是非线程安全的,多个线程put的时候造成了某个key值Entry key List的死循环,问题就这么产生了。
当另外一个线程get 这个Entry List 死循环的key的时候,这个get也会一直执行。最后结果是越来越多的线程死循环,最后导致服务器dang掉。我们一般认为HashMap重复插入某个值的时候,会覆盖之前的值,这个没错。但是对于多线程访问的时候,由于其内部实现机制(在多线程环境且未作同步的情况下,对同一个HashMap做put操作可能导致两个或以上线程同时做rehash动作,就可能导致循环键表出现,一旦出现线程将无法终止,持续占用CPU,导致CPU使用率居高不下),就可能出现安全问题了。
三种方法解决此问题
1.Hashtable替换HashMap
private Hashtable map = new Hashtable();
替换
private HashMap map = new HashMap();
说明:
Hashtable 是同步的,但由迭代器返回的 Iterator 和由所有 Hashtable 的“collection 视图方法”返回的 Collection 的 listIterator 方法都是快速失败的:在创建 Iterator 之后,如果从结构上对 Hashtable 进行修改,除非通过 Iterator 自身的移除或添加方法,否则在任何时间以任何方式对其进行修改,Iterator 都将抛出 ConcurrentModificationException。因此,面对并发的修改,Iterator 很快就会完全失败,而不冒在将来某个不确定的时间发生任意不确定行为的风险。由 Hashtable 的键和值方法返回的 Enumeration 不是快速失败的。
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误做法:迭代器的快速失败行为应该仅用于检测程序错误。
2.Collections.synchronizedMap将HashMap包装起来
private Map map = Collections.synchronizedMap(new HashMap());
替换
private HashMap map = new HashMap();
说明:
返回由指定映射支持的同步(线程安全的)映射。为了保证按顺序访问,必须通过返回的映射完成对底层映射的所有访问。
在返回的映射或其任意 collection 视图上进行迭代时,强制用户手工在返回的映射上进行同步:
Map m = Collections.synchronizedMap(new HashMap());
...
Set s = m.keySet(); // Needn't be in synchronized block
...
synchronized(m) { // Synchronizing on m, not s!
Iterator i = s.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
不遵从此建议将导致无法确定的行为。
如果指定映射是可序列化的,则返回的映射也将是可序列化的。
3.ConcurrentHashMap替换HashMap
private ConcurrentHashMap map = new ConcurrentHashMap();
替换
private HashMap map = new HashMap();
说明:
支持检索的完全并发和更新的所期望可调整并发的哈希表。此类遵守与 Hashtable 相同的功能规范,并且包括对应于 Hashtable 的每个方法的方法版本。不过,尽管所有操作都是线程安全的,但检索操作不必锁定,并且不支持以某种防止所有访问的方式锁定整个表。此类可以通过程序完全与 Hashtable 进行互操作,这取决于其线程安全,而与其同步细节无关。
检索操作(包括 get)通常不会受阻塞,因此,可能与更新操作交迭(包括 put 和 remove)。检索会影响最近完成的更新操作的结果。对于一些聚合操作,比如 putAll 和 clear,并发检索可能只影响某些条目的插入和移除。类似地,在创建迭代器/枚举时或自此之后,Iterators 和 Enumerations 返回在某一时间点上影响哈希表状态的元素。它们不会抛出 ConcurrentModificationException。不过,迭代器被设计成每次仅由一个线程使用。
相关推荐
HashMap为什么是线程不安全的?如何解决HashMap的线程不安全问题?
NULL 博文链接:https://flyfoxs.iteye.com/blog/1198030
经常会看到说HashMap是线程不安全的,ConcurrentHashMap是线程安全的等等说法,不禁有个疑问,什么是线程安全?什么样的类是线程安全的? 1.什么是线程安全性(what) 线程安全定义,最核心是正确性, 正确性:多个...
这就有可能导致A线程和B线程同时对一个数组扩容,A线程扩容后替换掉老数组,这时B线程使用的数组实际上是A线程扩容后的数组,就会产生线程安全问题。 死锁原因 比如,当前集合数组长度为2,已经有两个元素被放在了...
高级程序员必会的HashMap的线程安全问题,适用于0~2年的
HashMap源码分析系列-第四弹:HashMap多线程解决方案.docx
哈希映射线程测试使用 Maven 构建和运行 mvn exec:java
HashMap在多线程的环境下是不安全的,没有进行加锁措施,所以执行效率快。如果我么需要有一个线程安全的HashMap,可以使用Collections.synchronizedMap(Map m)方法获得线程安全的HashMap,也可以使用...
Java标准库中的一些类如ArrayList、HashMap和SimpleDateFormat,都是非线程安全的,在多线程环境下直接使用它们可能导致一些非预期的结果,甚至是一些灾难性的结果。一般来说,Java标准库中的类在其API文档中会说明...
Java集合多线程安全 线程安全与不安全集合 线程不安全集合: ArrayList LinkedList HashMap HashSet TreeMap TreeSet StringBulider 线程安全集合: Vector HashTable Properties 集合线程安全...
多线程环境下,建议使用 ConcurrentHashMap,或者使用 Collections.synchronizedMap(hashMap) 将 HashMap 转成线程同步的。 只能使用关联的键来获取值。 HashMap 只能存储对象,所以基本数据类型应该使用其包装器...
HashMap底层实现原理HashMap与HashTable区别HashMap与HashSet区别。...因此,在多线程环境下,HashTable比HashMap更安全,但是性能可能较差。此外,HashMap允许将null作为键和值使用,而HashTable不允许。
25 多线程、多平台环境中的跟踪.mht 26 使用 ConTest 进行多线程单元测试.mht 27 实现非阻塞套接字的一种简单方法.mht 28 基于事件的NIO多线程服务器.mht 29 驯服 Tiger 并发集合.mht 30 Java5 多线程实践.mht ...
list 转化成hashmap例子 java程序
HashMap数据结构,HashMap的构造方法,HashMap的put,HashMap的get
HASHMap迭代集合的例子好用,逻辑算法
HashMap的存储结构 HashMap内部采用数组和链表的方式存储数据,每个元素都包含...HashMap通过synchronized关键字实现线程安全,确保多线程环境下的数据一致性和并发访问的安全性,避免潜在的竞争条件和数据不一致问题。
hashMap存储分析hashMap存储分析
非线程安全:如果多个线程同时访问同一个HashMap实例,可能会导致数据不一致的问题。因此,在使用HashMap时需要进行同步处理或者使用线程安全的HashMap实现类。 动态扩容:当HashMap中的元素数量超过了容量(默认为...
深入浅出HashMap,源码分析, HashMap存取实现,数据结构