把Java的nio坑逐个踩一遍
epoll是个好东西好多地方都在直接或间接的用,nginx用event库用nio用你用我用大家用。LT模式省心ET模式牛掰,处理得当效率那真是杠杠的,C、C++的用法可以参考“项目”通用库https://git.oschina.net/gonglibin/GlbLib-1.0.0中的封装,也可以参考“代码片段”中的用例https://git.oschina.net/gonglibin/codes/kev675liw8unz3j4psft134。由于一个潜在的项目迁移计划导致未来存在基于java技术路线中涉及线程池大并发长(短)连接的业务,所以一个框架就显得特别重要,兵马未动粮草先行。其实不同的业务包含各自不同的应用场景,就通信而言其实差别有时候还是挺大的,同样TCP连接,有的长有的短,有的求多应少,有的求少应多,还有一问一答的,更有一言不合就shutdown的,对底层资源的管理不上心有时候真不行,大半夜没起过夜绝对不算老司机。
说说nio踩过的坑吧,下面列举的仅仅是让发动机托底的大坑,其中崴脚的小坑更是不计其数,首先打开地址https://git.oschina.net/gonglibin/codes/2jagu36pq7dwsc1eylkr916。首先注意一点的是nio的许多操作不是线程安全的,用不对各种坑有的踩了。大致思路首先创建TCP服务,构造里建线程池,主进程里给selector对象画个圈圈,这个对象的所有操作限制在listen方法里,把对业务的处理全部浸泡在读事件中,为啥读完不注册写信号让写事件处理呢,一会后面说吧,要是忘了千万别提醒我。
坑一,cli.register(sel, SelectionKey.OP_READ, ByteBuffer.allocate(TU_SERSIZE)),在注册新连接的时候顺带把allocate的buffer赋值给SelectionKey对象的attachment,接下来read并判断读取长度,不大于零和对端一起关闭,否则把SelectionKey对象往线程池里一派发就完事了,流程自己继续跑它自己的路,代码看起来简洁潇洒大气木毛病,正嘚瑟着问题来了,对端一关闭傻眼了,新请求直接把attachment擦除了,所以还是别顺带了,老老实实给每个任务开个buffer吧。
坑二,这些年搬砖一直追求精益求精对资源少占少用,C、C++一个套接字底层对应两块buffer,一读一写井水不犯河水相安无事,ByteBuffer对象是环形buffer,通过flip方法实现读写缓存的逆转,把position游标值和limit提交值一变轻松搞定,好是好但在线程池里维护一个连接上的各个buffer就够费劲的这还非要把读写合并一堆儿,真考验人呀,加锁呗,行是行就是显得low。
坑三,把SelectionKey对象的实例作为线程入口参数派发,表面看没毛病,但在完成各个事件处理以后重新注册监听事件的时候线程间同步又成了问题,其实这个坑已经解答了上面的疑问,读写分离按需注册固然完美,但每个线程的执行时间是不同的,CPU时间片调度也只能相对均衡,保不齐谁早点谁晚点,一个close操作秒完,那边任务才跑一半,咔嚓一下就把注册的监听事件给改了,结果这边的注册失效了,傻了吧唧还等呢,左等不来右等不来结果下班了。
坑四,SocketChannel对象关闭以后把SelectionKey实例的valid记做false,打标记倒是无所谓啦,不过无效的对象是不是就别再放在监听集合里处理是不是好点呀,免得每次还要判断,不判断就抛出一个异常,你看人家C库epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)设置EPOLL_CTL_DEL清理的干干净净,或许这么玩有人家自己的道理吧,相信别人这么做是有道理的。
把接收缓存和频道对象作为入参传给线程池进行任务处理,保证每次请求都能被唯一的指派,强调服务的一对一特性,处理完流程内部直接返回了,能做成读写异步自然好呀,不过写这个框架是有私心的,业务指向十分明确,其他的需求还是另起炉灶或许更好一些。最后还是要强调一下,不同的业务对通信方式的要求不尽相同,所以一个框架解决不了所有问题,还是应该具体问题具体分析。
难得一本正经的写个东西浅显地剖析一个不是问题的问题,口号喊起来:励斌出品,必是精品,欧耶!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java内存区域与内存溢出异常
Java内存区域与内存溢出异常 觉得书上有一句话很有意思 Java与C++之间有一堵有内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来 运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁 根据Java虚拟机规范,Java虚拟机所管理的内存将会包括以下几个运行时数据区域 Java虚拟机运行时数据区 下面将大致介绍Java内存区域的分类和不同以及抛出内存溢出异常的原因 内存泄漏是造成内存溢出的主要原因 程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节码指令、分之、循环、跳转、异常处理、线程恢复等基础功能都需要通过这个计数器来完成 Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻。一个处理器都只会执行一条线程中的指令,因此为...
- 下一篇
讲真:序列化必读
序列化是啥啥是序列化为啥要序列化把啥序列化不序列化行不行?问题真多,馒头要一口一口吃问题要一个一个回答。所谓序列化就是格式化,感觉哪不对劲,是约定的格式化,及格了,能够满足正向操作和逆向操作的约定格式化,良好了,能高效稳定满足正向操作和逆向操作的约定格式化,满分了,能提供轻松扩展并高效稳定满足正向操作和逆向操作的约定格式化,耶~恭喜你都会抢答啦。能够支持PHP语言的......,歇菜没你事别瞎掺和。 格式化好理解,标题正中段落开头空两个字段落结束换行这就是格式化,修饰词约定的意思是说你看见这个格式就知道哪个是标题哪个是正文,满足正向操作和逆向操作意思是说能转换过来也必须能丝毫不差原样还原回去,高效好理解一次操作就占一半以上系统开销,数据中一大半是冗余信息估计没人爱用,稳定可靠不能动不动就掉链子,一会儿异常了一会儿解析失败了一会儿又丢数据了。序列化是啥啥是序列化我解释完了,觉得不对的下面就不用看了。 为啥要序列化,一提这茬儿内心满满的委屈一把辛酸泪呀。早年电脑不联网的时候数据都在本地格式化着用,后来联网以后因为硬件和网络等差异,高低字节序主机字节序网络字节序各种问题扑面而来,接着就是各种...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装Docker,最新的服务器搭配容器使用
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7设置SWAP分区,小内存服务器的救世主