java “反击”造成的死锁

java “反击”造成的死锁

填坑填坑。

之前写了几篇关于死锁的小文章,算是加强了概念。今天尝试用游戏中的一个场景来加深对死锁的理解。

 

一、反击

反击:玩家A对玩家B发起攻击时,玩家B也对玩家A发起攻击

尝试模拟一下两个玩家互相攻击的场景:

testPlayer.java

看过前几篇文章的同学应该知道,attack从始至终只需要获取“被攻击者”的对象锁,不会出现两条线程相互等待的情况,因此不会发生死锁。

那么问题来了,上面的代码只是两个玩家在单纯地互殴而已,如何改加入反击的情况呢?

最简单的方式,改动一下attack方法:

是不是有种奇怪的既视感…调整下顺序,就变成了这样:

这不又变成了经典死锁吗?原来的攻击代码虽然简陋,最起码还能跑,改成反击之后甚至不能运行!

二、问题的解决

在这篇文章中,我们讨论了如何解决锁顺序死锁问题,这次我们就试试看。

java 锁顺序死锁

改写代码:

testPlayer.java

运行结果为:

因为加入了反击,每个玩家会在500次攻击中受到500次反击,并且被攻击500次,血量为500 – 500*1 – 500*1 = -500,应该很好理解。

很明显,关键的代码为:

在本场景中,A攻击B时,因为A的id=1 < B的id=2,所以先获取B的对象锁,再获取A的对象锁。B攻击A时,因为B的id=2 > A的id=1,所以依然先获取B的对象锁,再获取A的对象锁。

综上,本场景不会出现相互等待的情况,故不会出现死锁。

在我开发过的一个游戏项目中,玩家的id类型为long,游戏中npc的id也为long,怪物的id也为long…游戏中存在着大量的“游戏对象”,就存在着大量的不可重复的id(因为涉及跨服pk,全服的玩家id必须通过rpc统一分配,避免id重复),导致每个对象的id很长很长。

当时我不能理解为什么要把id要用long类型,搞得这么长…现在一想可能就是为了“更容易地控制锁顺序”。A砍B一刀,一个大于小于就决定了锁顺序,战斗流程就比较简单。如果id弄成string,就要设计一套hash方案决定锁顺序,搞不好还会出现死锁。因此,id为long是最简单直接的选择。

三、总结

终于知道为什么战斗的代码要涉及这么多锁了…

发布者

xie4ever

发表评论

电子邮件地址不会被公开。