加锁的位置 (eq:map<key,map<>> 双集合 怎么 只加锁 在用到的对象位置,而不是把整个集合锁住)

比如上边的map里套map 定义变量为data,例如组队副本 npc 为1 下有众多房间 即Map<1,<roomId,room>> ,处于多线程下,一个线程在 npc为1的下边建一个房间,房间id为1即Map<1,<1,room>> ,这时候另外一个线程同时也要在 npc为1下建一个房间id为2的房间 即 Map<1,<2,room>> ,那我代码里肯定是 先 

<roomId,room> rooms  =  data.get(1); 假如 rooms为null,则 rooms = new Map<roomId,room>(); 然后 put到 data里,即 data.put(1,rooms);但是问题就在这,多线程下, 怎么能保证 多个线程 只new 一个 rooms,而不是每个线程 都 各自new 一个rooms,这就是要加锁了,但是要想保证只new 一个rooms出来,那必须 要在整个data上加锁,这问题就来了,我明明就只想保证new 一个rooms出来,结果我把整个data锁起来,假如这边npc 的id 不仅仅有1,还有其他2,3,4,5,6,7... 这时候怎么办,如果所整个data,那其他npc 对应的rooms 也将被锁住,性能消耗出现问题,那怎么办呢,好办,先把 data 定义成 ConcurrentHashMap,即

ConcurrentHashMap data = null; 然后利用 ConcurrentHashMap 中的 putIfAbsent()方法,ConcurrentHashMap为线程安全的集合,putIfAbsent()也为线程安全的原子级 操作,所以
可以 直接 data.putIfAbsent(1,new <roomId,room>); putIfAbsent()含义即 key对应的值 如果不存在的话就new一个出来然后放进去第一次putIfAbsent()直接返回null,
同普通map 的put方法一样,如果map中没有key对应的值,第一次put时返回null,以后每次由于有值了返回才是 key对应的那个存在的值,这里putIfAbsent()第一次如果没有就new 值然后放进去 后
返回同理也是null,以后get才会拿到key对应的值,如果存在就返回旧的值(不会new,直接用旧的返回)。分析一下,假如2个线程都同时来npc为1的rooms,2个拿到的rooms假如都是null,都需要
new 新的rooms,第一个线程先putIfAbsent()时,由于 putIfAbsent() 方法是原子级的线程安全,使得第二个线程调用 putIfAbsent()时处于阻塞状态,直到第一个线程调用putIfAbsent()
结束后释放锁后第二个线程才能够调用 putIfAbsent(),由于第一个已经new一个新的而且放入到data里了,第二个线程调用putIfAbsent()时从data里能拿到值了所以就不会再new新的,而是直接就用
第一个线程 new的那个值,这样就保证了多线程建的同步安全。

补:还有一种恶心的同步加锁,比如 组队副本中 有Map<npcId,Map<roomId,room>> 这个结构定义变量为data, 这个 data 有加入房间(将玩家加入到room中)、有退出房间(room 中删除玩家,
room中没人时这个room还要从data里remove)、有 快速匹配房间 操作(将room从data中remove掉),这三种操作都会修改room,有的是给room中加人,有的是给room中减人和删除room,有的是
直接就删除room,这时候肯定就要加锁了,比如一个线程在我给room加人时,另外一个线程刚好把这个room给从data中remove 掉了,那就算第一个我的线程 给room中加人成功了又有什么用,你加成功了
结果这个房间都从data中remove掉了,也就是你是在第一个线程加到room里,结果第二个线程把room 从data中remove掉,那第一个线程加的那个人就进入二次元空间了,因为那个room在data中的引用被
remove了,room整个是被遗弃了,room整个被删除进入回收状态,room整个进入二次元了,那room里的人肯定更是进入二次元空间了,加入这时候在加人时 有 人每天加入房间次数的限制,那第一个线程
处理的那个人就会被坑掉一次,这样数据就错乱了,所以就要加锁,那这个锁又怎么加呢? Map<roomId,room> rooms = data.get(npcId); 难道对这个rooms进行加锁? 那假如我在加入一个房间,
结果把整个npc下对应的rooms都锁住了,其他玩家的一些不是 加人都房间的操作(比如 退出房间等操作)也是不能做了,那不加整个rooms的锁的话又怎么能不会造成数据错乱呢? 解决方法有一个思路,
就是操作哪个共享的对象就对哪个对象加锁,这里加人是操作单个room,那就对整个room加锁才是最合法的做法,那又怎么保证同步数据安全呢?解决方法是在 room中加一个状态,即我加锁在这单个room上,
然后在同步块中 进行这个单个room 中那个状态的判断,由于其他线程操作时也会锁住这个单个room,所以这个状态的修改肯定是只能由一个线程同时修改,所以就不会数据错乱问题了。

posted @ 2015-04-13 21:56  wzhanke  阅读(866)  评论(0编辑  收藏  举报