HashMap在多线程环境下偶然造成InfiniteLoop导致程序宕机
2016-07-11 16:09
411 查看
在多线程环境下,非线程安全的hashmap可能会造成的问题
package littlehow.map; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * HashMapInfiniteLoop hashmap死循环造成的cpu 100% * 有时候,会出现很奇怪的现象,那就是程序运行一段时间会出现宕机,重启之后就没事儿了 * 具体什么原因也很难查找。 * 在高并发环境下,因为hashmap的线程不安全性,可能会引起上述现象 * 现在就看看,这种现象如何发生的 * * @author littlehow * @time 2016-07-11 10:16 */ public class HashMapInfiniteLoop { //初始长度为4的hashmap private Map<Integer, String> map = new HashMap<Integer, String>(4); //线程数量 final int count = 200; final CountDownLatch countDownLatch = new CountDownLatch(count); @Before public void init() { //因为hash计算结果类似于mod,所以我们可以事先准备冲突的hash对 //rehash的源码,可以看出当size==threshold也就是4的时候会进行resize, //从而重新分布元素 /** if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } for (Entry<K,V> e : table) { 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); e.next = newTable[i]; newTable[i] = e; e = next; } } */ // 3 11 7 15 put后这三个都出现了hash冲突,所以结构为 3 -> 11 -> 7 -> 15 的链表 //当再put19时,又出现冲突, 这时候就需要扩容。 // 正常情况形成两个链表 11 -> 3 -> 19 和 15 -> 7 //但是当执行线程以执行到如 Entry<K,V> next = e.next;获取到的next=11时线程被挂起; //线程二来执行成功,线程一继续,e=7,next=15,进行rehash,执行到 //newTable[i] = e = 7; e = next = 15,而线程二已经排好 15 -> 7, //很明显线程一形成的链表为7 -> 15,合并结果就是 15 -> 7 -> 15形成了环形链表 //这时候get(23)就会一直查找下去,因为元素一直拥有next。从而导致 infinite loop //resize后大小为8 map.put(3, "littlehow"); map.put(11, "color wolf"); map.put(7, "black dog"); map.put(15, "blue blood"); } @Test public void conflict() { /** * 想要出现Entry<K,V> next = e.next;之后挂起线程非常困难,在并发量超级大的情况下有可能会出现 * 所以没有出现死循环是最可能的现象,生产环境往往是不可预测的 * 在多线程环境下使用map这样结构的类有两点建议: * 1.使用ConcurrentHashMap * 2.如果确定map元素多少的情况,最好初始化map尽量大,避免rehash */ for (int i = 0; i < count; i ++) { new Thread("thread-" + (i + 1)) { @Override public void run() { map.put(19, "new person"); countDownLatch.countDown(); } }.start(); } try { TimeUnit.SECONDS.sleep(1); countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } @After public void over() { System.out.println("value=" + map.get(23) + " , size=" + map.size()); } }
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android Google Map获取地理位置信息的方法
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- Spark RDD API详解(一) Map和Reduce
- PropertyChangeListener简单理解
- Python中map()函数浅析
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序