今天遇到了个问题,在全局服务器重启的时候,原有的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); }