问题一:当A,B同时攻击C时,需要对C进行减血逻辑。如果A,B是在不同线程执行这个逻辑的,那么会引发C的血量异常问题。

解决方案:
将减血逻辑放在一个单独的线程执行。具体操作为,首先,创建一个MainMsgProcessor的单例工具类

public class MainMsgProcessor {static final MainMsgProcessor instance = new MainMsgProcessor();private MainMsgProcessor(){}public static MainMsgProcessor getInstance(){return instance;}public void process(){//消息处理逻辑}}

我们用这个类统一处理消息,由他接管Netty的多线程handler处理消息。
怎么使用单线程处理呢?这里可以使用Java util concurrent包下面的

private final ExecutorService es = Executors.newSingleThreadExecutor();

创建一个单线程的线程池,将消息逻辑交由这个线程处理

因此process方法修改如下

public void process(){es.submit(()->{//处理消息逻辑});}

这样对业务逻辑的处理都会被交给这个单线程的线程池来处理。这样既避免了线程不安全的问题,又避免了业务逻辑与线程逻辑交杂的混乱。因为对于这个问题,我们可以在扣血的方法加上synchronized关键字,但是这样一是线程争夺锁导致效率降低,二是业务逻辑与线程安全逻辑混合,增加了编码的复杂度

线程池的内部是一个阻塞队列,因此这个问题的总体抽象如下图所示


可能有的人会有这样的疑问:将所有消息处理逻辑放在单线程,会不会导致并发量太低,速度慢?其实不然,因为游戏服务器速度的瓶颈都出现在IO和网络请求,或者是内存占用率太高,在业务逻辑的计算这一块单线程不见得会比多线程慢

问题二:在解决了上述问题之后,如果我们的登陆操作的消息处理也被放入同一个线程,那么每次用户的登陆请求都涉及到对数据库的访问,会导致其他请求被阻塞

解决方案:对IO实行多线程管理。IO操作被放入其他线程当作一个异步操作

我们首先定义一个静态的单例异步操作处理器,同样,我们也给他一个单线程的线程池

public final class AsyncOperationProcessor {static final AsyncOperationProcessor instance = new AsyncOperationProcessor();private AsyncOperationProcessor(){}private final ExecutorService es = Executors.newSingleThreadExecutor();public static AsyncOperationProcessor getInstance(){return instance;}/*** 执行异步操作** * @param r*/public void process(Runnable r){if(r==null){return;}es.submit(r);}
}

这样,我们可以把登陆部分的代码逻辑封装入这个类中,让登陆逻辑异步执行

AsyncOperationProcessor.getInstance().process(()->{//登陆逻辑});

当我们执行完登陆逻辑的时候,我们还需要拿到登陆逻辑的返回结果,这也就是所谓的异步:执行完了之后会将结果交到你的手中。那么,我们如何拿到返回结果呢?在这个例子中,假如我们登陆操作返回了一个user对象,我们怎样拿到这个对象呢?这也是异步调用的通用问题

首先,第一种很容易想到的思路是,在外部定义一个变量,然后执行完之后将结果赋予这个外部的变量。

public User userLogin(String username,String password){User user = null;AsyncOperationProcessor.getInstance().process(()->{//登陆逻辑user = myUser});return user;
}

这个思路是行不通的,因为process是在另一个线程中执行的,也就是说,当前线程并不会等另一个线程全部执行完才往下运行,所以我们返回的user很有可能是登陆逻辑没有执行完全的user,也就是一个null

解决这个问题的正确做法之一是,我们不能指望从userLogin这个方法立刻拿到返回值,而是传入一个回调函数,当我们计算出结果的时候,将结果作为参数传给这个回调函数并调用它。

public void userLogin(String username,String password,
Function<User,void> callback){User user = null;AsyncOperationProcessor.getInstance().process(()->{//登陆逻辑if(callback!=null){callback.apply(myUser)}});
}

那么我的userLogin调用的过程中,传入的参数应该是这样的

LoginService.getInstance().userLogin(username,password,(user)->{//当异步过程执行完后,会将计算结果作为user 传入//登陆完毕的后续操作代码
}
)

这样就算完美了吗?其实不然,因为我们的回调函数实际上是在我们的异步线程中被调用的,也就是说,业务逻辑也是在异步线程中执行的,而这又会引发数据的脏读写问题,而我们理想的情况应该是,异步线程将执行完毕的结果传给我们的消息处理线程,我们用那个线程来执行业务逻辑。

那么,针对这个问题的解决方案是什么呢?
首先,我们定义一个接口 :

public interface IAsyncOperation {/*** 执行异步操作*/void doAsync();/*** 执行异步操作完成之后的逻辑* 可以不实现*/default void doFinish(){}
}

我们对我们上面的异步处理器类的process方法进行修改

public final class AsyncOperationProcessor {static final AsyncOperationProcessor instance = new AsyncOperationProcessor();private AsyncOperationProcessor(){}private final ExecutorService es = Executors.newSingleThreadExecutor();public static AsyncOperationProcessor getInstance(){return instance;}/*** 执行异步操作*** @param op*/public void process(IAsyncOperation op){if(op==null){return;}es.submit(()->{//执行异步逻辑op.doAsync();//执行完成逻辑op.doFinish();});}
}

同时,我们在MainMsgProcessor类中加入一个重载的process方法

public class MainMsgProcessor {static final MainMsgProcessor instance = new MainMsgProcessor();private final ExecutorService es = Executors.newSingleThreadExecutor();private MainMsgProcessor(){}public static MainMsgProcessor getInstance(){return instance;}public void process(){es.submit(()->{//处理消息逻辑});}public void process(Runnable r){if(r==null){return;}es.submit(r);}
}

随后,我们更改AsyncOperationProcessor的process方法,将doFinish提交给主线程去写

/*** 执行异步操作*** @param op*/public void process(IAsyncOperation op){if(op==null){return;}es.submit(()->{//执行异步逻辑op.doAsync();//执行完成逻辑MainMsgProcessor.getInstance().process(()->{op.doFinish();});});}

这样一来,我们只需要实现这个接口,将登陆逻辑放入doAsync,而将完成登陆后回调放在doFinish中执行。

注:AsyncGetUserEntity为实现IAsyncOperation接口的类,其中的doAsync执行的是登陆逻辑

问题三:这样一来虽然将登陆放到别的线程中执行,那么阻塞的问题不是依然没有解决吗?因为IO线程只有一个

解决方案:在线程池中多开辟几个线程

问题四:如果用户连按两次登陆按钮,两个线程会同时开始登陆过程,有可能造成数据库的数据发生异常,一个用户被注册了两遍

解决方案:对用户名进行hash,使得每个用户的任务只能提交给某个固定的线程

Java游戏服务器架构的并发问题及解决方案相关推荐

  1. 【阅读笔记】Java游戏服务器架构实战

    [阅读笔记]Java游戏服务器架构实战 书籍链接:Java游戏服务器架构实战 作者提供的源码链接:kebukeYi / book-code 这里对书籍中比较重要的知识点(精华部分)进行摘录(总结) 文 ...

  2. 【游戏开发】《Java游戏服务器架构实战》项目在windows上部署

    [游戏开发]<Java游戏服务器架构实战>项目在windows上部署 文章目录 [游戏开发]<Java游戏服务器架构实战>项目在windows上部署 一.配置项目基础环境 二. ...

  3. ioGame 网络游戏服务器框架 (java)、java游戏服务器、netty 集群分步式的网络游戏服务器

    ioGame 国内首个基于蚂蚁金服 SOFABolt 的 java 网络游戏服务器框架:无锁异步化.事件驱动的架构设计 通过 ioGame 可以很容易的搭建出一个集群无中心节点.有状态多进程的分步式游 ...

  4. 游戏服务器架构设计的一些整理

    一.前言 没有最好的架构,只有最适合自身业务的架构. 首先我们应该确定的是大的架构方向:分布式 / 单应用+负载均衡,这两种架构设计直接影响后续的网络层.缓存层.数据层.业务层的设计.笔者这两种架构的 ...

  5. Java游戏服务器成长之路——感悟篇

    又是一个美好的周末啊,现在一到周末,早上就起得晚,下午困了又会睡一两个小时,上班的时候,早上起来喝一杯咖啡,然后就能高效的工作一整天,然而到了周末人就懒散了,哈哈. 最近刚跳槽,到新公司已经干了有两周 ...

  6. 学习JAVA游戏服务器开发需要了解的情况

    一,游戏服务器开发的工作介绍 近来遇到有很多人想从其它开发领域转到游戏服务器开发行业上来,他们或许觉得游戏服务器开发工资高,或许觉得做游戏服务器需要掌握的技术更高级,可以锻炼自己,或许觉得想换个环境等 ...

  7. 百万用户同时在线游戏服务器架构实现.doc 基于epoll 通信模型

    http://wenku.baidu.com/view/02033d0af78a6529647d53fc.html 百万用户在线网络游戏服务器架构实现 一.            前言 事实上100万 ...

  8. java游戏服务器必备

    推荐阅读: 我的CSDN 我的博客园 QQ群:704621321 对于一个新手,想接触游戏服务器,一定会有个疑问--使用Java开发服务器需要学习什么? Java语言,由于学习成本低,开发速度快,稳定 ...

  9. 通用游戏服务器架构设计

    经过研究多个框架和自己经历的项目,我总结出来一套较为通用的,以8c16g机器来说,如何设计出来一套单进程多线程.分区分服的java游戏服务器框架呢? 1.逻辑层(带养成数据的模块,io密集) 线程池: ...

最新文章

  1. 又快又简单的sql2005分页存储过程
  2. perl中Net::FTP帮助文档
  3. mybatis整体架构
  4. python学习笔记之装饰器、递归、算法(第四天)
  5. FJ的字符串(字符串)
  6. 8.1 段子中“酷毙”了的IT行业——《逆袭大学》连载
  7. Win7 64位系统下Auto CAD 2010注册激活,出现警告:Make sure you can write to current directory...
  8. 配音软件哪个好?这三款很火的配音软件,简直是短视频后期配音必备
  9. nginxconsul
  10. windows10未激活更换壁纸
  11. 【读书笔记】数学之美
  12. Dicom学习之一:大尾和小尾LittleEndian/BigEndian
  13. win10照片查看器_Win10小技巧,帮你事半功倍
  14. ***我是如何通过华为面试的?群面+技术面+综合面+英语面(Android岗)***
  15. mac 上安装 ettercap-gtk,driftnet
  16. php-调用阿里云第三方短信接口
  17. 数据库提示错误代码ora 01688
  18. 运行命令打开电脑程序 快捷键
  19. isqlplus 连接标识符 (奇怪的问题)--(解决)
  20. ENVI5.1新增波谱库及波谱曲线工具

热门文章

  1. 使用vscode利用vue脚手架创建项目每次修改代码都会频繁编译
  2. 基于手机音频通信应用
  3. 游码编程之Python代码应用
  4. 项目经理修炼之道(1) -- 给软件开发建模 .
  5. 給windowsXP穿上Linux Ubuntu的漂亮馬甲 1
  6. 鸿蒙 谷歌怕了,鸿蒙系统展示了华为的野心,难怪谷歌害怕
  7. mysql执行计划extra_四、MySQL优化之explain执行计划的extra属性
  8. 看过之后,今年的深信服创新大会确实有点儿不一样……
  9. 使用pyfinance进行证券收益分析!金融界的一大帮手!
  10. 常用的HDFS Shell命令及解析