HashMap进行put操作时遇到的并发问题

简介: HashMap进行put操作时遇到的并发问题

今天遇到了个问题,在全局服务器重启的时候,原有的4游戏逻辑服务器会马上再注册过来,通过多线程对HashMap进行操作,结果从后续的日志发现,id=1的数据丢失了。查了一下原因,多线程环境下put还是可能会有问题 .

2013-10-22 18:59:55.098 INFO [pool-5-thread-3] - 注册房间开始 id=1,type=pve1003,ip=115.29.172.234,port=10104,count=0
2013-10-22 18:59:55.098 INFO [pool-5-thread-5] - 注册房间开始 id=3,type=pve1004,ip=115.29.172.234,port=10105,count=0
2013-10-22 18:59:55.098 INFO [pool-5-thread-4] - 注册房间开始 id=2,type=pvp1001,ip=115.29.172.234,port=10101,count=0
2013-10-22 18:59:55.099 INFO [pool-5-thread-1] - 注册房间开始 id=4,type=pve1002,ip=115.29.172.234,port=10103,count=0
2013-10-22 18:59:55.102 INFO [pool-5-thread-3] - 注册房间完成  id=1
2013-10-22 18:59:55.103 INFO [pool-5-thread-4] - 注册房间完成  id=2
2013-10-22 18:59:55.104 INFO [pool-5-thread-5] - 注册房间完成  id=3
2013-10-22 18:59:55.105 INFO [pool-5-thread-1]- 注册房间完成  id=4


void addEntry(int hash, K key, V value, int bucketIndex) {   
    Entry<K,V> e = table[bucketIndex];   
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);   
        if (size++ >= threshold)   
            resize(2 * table.length);   
    }


发生在index相同的情况下,大家拿到的链头可能不是最新的,后一个会直接覆盖了前一个
而且当多条线程检测到容量超过负载因子时,会能发生多次resize。

小结一下:remove与put都是一样的,由于大家拿到的不是最新链头,只要大家在Entry数组的index相同时(经过hash后的index),就有可能出现后一个覆盖前一个的操作,即前一个的操作无效。
可能产生的现象会是:
1)put进行的data有可能丢失了
2)一些通过remove(Object key)删除掉的元素(返回删除成功)又出来了。
3)多线程检测到HashMap容量超过负载因子时会进行多次的resize,由于要rehash,所以消耗的性能也是巨大的。


考虑到这个方法只有在起服务器的时候会用到,而且,后续的关于rooms和areas的map读操作比较频繁,所以通过加同步块解决,比较合适!

// Room缓存 角色Id -- Room对象
  private final Map<Integer, Room> rooms = new HashMap<Integer, Room>();
  private final Map<String, List<Integer>> areas = new HashMap<String, List<Integer>>();
public synchronized Room registRoom(Channel channel, String type, int port,
      String clientIp, List<PlayerDTO> playerList) {
List<Integer> list = areas.get(room.getType());
    if (null == list) {
      list = new ArrayList<Integer>();
      areas.put(room.getType(), list);
    }
rooms.put(room.getId(), room);
}


目录
相关文章
|
7月前
|
存储 索引
HashMap 在 get 和 put 时经过哪些步骤
HashMap 在 get 和 put 时经过哪些步骤
73 0
|
2月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
64 5
|
2月前
|
存储 索引
让星星⭐月亮告诉你,HashMap在put数据时是如何找到要存放的位置的?
HashMap 是一种常用的键值对存储结构,其底层采用数组+链表+红黑树实现。本文探讨了 HashMap 在插入键值对时如何确定存放位置。通过分析 `put` 方法的源代码,重点解析了哈希码的计算过程和数组索引的确定方法。哈希码通过 `hashCode()` 方法和位运算优化,确保均匀分布,从而减少哈希碰撞,提高性能。最终,通过 `(n-1) & hash` 计算出数组索引,确保键值对被正确存放到指定位置。
41 2
|
3月前
|
设计模式 安全 Java
HashMap底层原理:数据结构+put()流程+2的n次方+死循环+数据覆盖问题
假如有T1、T2两个线程同时对某链表扩容,他们都标记头结点和第二个结点,此时T2阻塞,T1执行完扩容后链表结点顺序反过来,此时T2恢复运行再进行翻转就会产生环形链表,即B.next=A;采用2的指数进行扩容,是为了利用位运算,提高扩容运算的效率。JDK8中,HashMap采用尾插法,扩容时链表节点位置不会翻转,解决了扩容死循环问题,但是性能差了一点,因为要遍历链表再查到尾部。例如15(即2^4-1)的二进制为1111,31的二进制为11111,63的二进制为111111,127的二进制为1111111。
HashMap底层原理:数据结构+put()流程+2的n次方+死循环+数据覆盖问题
|
2月前
|
存储 算法 索引
HashMap底层数据结构及其增put删remove查get方法的代码实现原理
HashMap 是基于数组 + 链表 + 红黑树实现的高效键值对存储结构。默认初始容量为16,负载因子为0.75。当存储元素超过容量 * 负载因子时,会进行扩容。HashMap 使用哈希算法计算键的索引位置,通过链表或红黑树解决哈希冲突,确保高效存取。插入、获取和删除操作的时间复杂度接近 O(1)。
32 0
|
5月前
|
存储 安全 Java
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
51 0
|
7月前
|
存储 Java 索引
【JAVA】HashMap的put()方法执行流程
【JAVA】HashMap的put()方法执行流程
HashMap中put()方法源码详解
HashMap中put()方法源码详解
|
存储 安全 索引
HashMap的put方法的具体流程
HashMap的put()方法用于向HashMap中添加键值对。
220 0
|
7月前
|
索引
HashMap的put方法的具体流程
HashMap的put方法的具体流程