模拟客户端向服务端发送消息:

客户端部分代码如下,当连接激活触发消息发送,采用线程池的形式,分多个线程向服务端发送同一消息

  @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel type: " + ctx.channel().getClass().getSimpleName());
NioSocketChannel socketChannel = (NioSocketChannel) ctx.channel();
ByteBuf byteBuf = Unpooled.copiedBuffer("test thread:", CharsetUtil.UTF_8).retain();
int x = 1;
Runnable runnable = new Runnable() {
@Override
public void run() {
socketChannel.writeAndFlush(byteBuf.duplicate());
System.out.println(Thread.currentThread().getName() + " " + byteBuf);
}
};
Executor executor = Executors.newCachedThreadPool();
for(int i = 0; i <= 10; i++){
executor.execute(runnable);
} }

服务端代码如下,接收到消息并打印

    @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf)msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
//ctx.write(in); // send the msg to client and no flush
}

服务端与客户端均未进行连接关闭操作(始终保持连接)。

服务端打印结果如下(只接受到部分客户端消息),常规思想,理应收到客户端发来的所有消息:10条 “test thread:”, 结果只收到两条

Server received: test thread:test thread:

原因:writeAndFlush()方法最终会调用AbstractChannelHandlerContext类的如下代码

private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = this.findContextOutbound();
Object m = this.pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
Object task;
if (flush) {
task = AbstractChannelHandlerContext.WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = AbstractChannelHandlerContext.WriteTask.newInstance(next, m, promise);
} safeExecute(executor, (Runnable)task, promise, m);
} }

方法逻辑大致如下:

1.获取channelPipeline中的head节点

2.获取当前channel的eventLoop对象

3.判断当前channel的eventLoop对象中的线程是否是当前线程

4.如果是EventLoop线程,则直接执行writeAndFlush方法,也就是执行写入并且刷新到channelSocket中去

5.如果不是EventLoop线程,则会创建一个AbstractWriteTask,然后将这个task添加到这个channel的eventLoop中去

原因:

分析到这里就可以总结问题的所在了,如果执行channel的writeAndFlush的线程不是work线程池中的线程,那么就会先将这个发送消息封装成一个task,然后添加到这个channel所属的eventLoop中的阻塞队列中去,

然后通过EventLoop的循环来从队列中获取任务来执行。一旦task添加到队列中完成,write方法就会返回。那么当下一个客户端再执行write方法时,由于msg内容是同一个对象,就会将前一个msg的内容给覆盖了。

从而就会出现发送给多个客户端的内容不同,但是接收到的内容是相同的内容。而本例中,执行channel的write方法的线程确实不是eventLoop线程,因为我们采用了线程池来处理业务,当channel发送数据给服务器之后,

服务器解析channel中发送来的请求,然后执行业务处理,而执行业务的操作是采用线程池的方式实现的,所以最终通过channel发送数据给客户端的时候实际的线程是线程池中的线程,而并不是channel所属的EventLoop中的线程。

总结:

Netty中的work线程池中的EventLoop并不是一个纯粹的IO线程,除了有selector轮询IO操作之外,还会处理系统的Task和定时任务。

系统的task是通过EventLoop的execute(Runnable task)方法实现,EventLoop内部有一个LinkedBlockingQueue阻塞队列保存task,task一般都是由于用户线程发起的IO操作。

每个客户端有一个channel,每一个channel会绑定一个EventLoop,所以每个channel的所以IO操作默认都是由这个EventLoop中的线程来执行。然后用户可以在自定义的线程中执行channel的方法。

当用户线程执行channel的IO操作时,并不会立即执行,而是将IO操作封装成一个Task,然后添加到这个channel对应的EventLoop的队列中,然后由这个EventLoop中的线程来执行。所以channel的所有IO操作最终还是

由同一个EventLoop中的线程来执行的,只是发起channel的IO操作的线程可以不是任何线程。

采用将IO操作封装成Task的原因主要是防止并发操作导致的锁竞争,因为如果不用task的方式,那么用户线程和IO线程就可以同时操作网络资源,就存储并发问题,所以采用task的方式实现了局部的无锁化。

所以线程池固然好用,netty固然强大,但是如果没有深入理解,稍有不慎就可能会出现意想不到的BUG。

netty EventLoop线程与当前线程的问题的更多相关文章

  1. eventloop & actor模式 & Java线程模型演进 & Netty线程模型 总结

    eventloop的基本概念可以参考:http://www.ruanyifeng.com/blog/2013/10/event_loop.html Eventloop指的是独立于主线程的一条线程,专门 ...

  2. Netty 应用程序的一个一般准则:尽可能的重用 EventLoop,以减少线程创建所带来的开销。

    Netty 系列一(核心组件和实例). - JMCui - 博客园 https://www.cnblogs.com/jmcui/p/9154842.html 阅读目录 一.概念 二.核心组件 三.实例 ...

  3. Netty源码细节IO线程(EventLoop)(转)

    原文:http://budairenqin.iteye.com/blog/2215896 源码来自Netty5.x版本, 本系列文章不打算从架构的角度去讨论netty, 只想从源码细节展开, 又不想通 ...

  4. Netty框架问题记录1--多线程下批量发送消息导致消息被覆盖

    业务背景 项目是基于Netty实现的实时课堂项目,课堂中老师需要对试卷进行讲解,则老师向服务器发送一个打开试卷信息的请求,服务器获取试卷信息,将试卷信息发送给所有的客户端(学生和老师). 发送给学生的 ...

  5. Netty源码解析一——线程池模型之线程池NioEventLoopGroup

    本文基础是需要有Netty的使用经验,如果没有编码经验,可以参考官网给的例子:https://netty.io/wiki/user-guide-for-4.x.html.另外本文也是针对的是Netty ...

  6. Netty(三):线程模型

    Netty中支持单线程模型,多线程模型,主从多线程模型. 1 单线程模型 在ServerBootstrap调用方法group的时候,传递的参数是同一个线程组,且在构造线程组的时候,构造参数为1,这种开 ...

  7. suging闲谈-netty 的异步非阻塞IO线程与业务线程分离

    前言 surging 对外沉寂了一段时间了,但是作者并没有闲着,而是针对于客户的需要添加了不少功能,也给我带来了不少外快收益, 就比如协议转化,consul 的watcher 机制,JAVA版本,sk ...

  8. Dubbo学习笔记8:Dubbo的线程模型与线程池策略

    Dubbo默认的底层网络通讯使用的是Netty,服务提供方NettyServer使用两级线程池,其中 EventLoopGroup(boss) 主要用来接受客户端的链接请求,并把接受的请求分发给 Ev ...

  9. 在netty3.x中存在两种线程:boss线程和worker线程。

    在netty 3.x 中存在两种线程:boss线程和worker线程.

随机推荐

  1. linux dubbo-admin-2.6.0 环境搭建

    1.去maven官网下载apache-maven-3.6.2-bin.tar.gz安装包 2.上传至linux服务器中 3.解压maven安装包 tar -zxvf apache-maven-3.6. ...

  2. IHostingEnvironment VS IHostEnvironment - .NET Core 3.0中的废弃类型

    原文: https://andrewlock.net/ihostingenvironment-vs-ihost-environment-obsolete-types-in-net-core-3/ 作者 ...

  3. 22.Python安装和卸载第三方模块方法

    安装和卸载第三方开源模块的步骤:下例,安装urllib3模块的步骤. 1.安装开源模块步骤: 按键盘windows键+r键,输出cmd回车.或开始->windows系统->命令提示符: 输 ...

  4. Java线程池学习总结

    一 使用线程池的好处 池化技术相比大家已经屡见不鲜了,线程池.数据库连接池.Http 连接池等等都是对这个思想的应用.池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率. 线程池提供了 ...

  5. C#反射与特性(六):设计一个仿ASP.NETCore依赖注入Web

    目录 1,编写依赖注入框架 1.1 路由索引 1.2 依赖实例化 1.3 实例化类型.依赖注入.调用方法 2,编写控制器和参数类型 2.1 编写类型 2.2 实现控制器 3,实现低配山寨 ASP.NE ...

  6. 在IIS上发布netcore项目

    保证电脑上有.net core sdk或者.net core runtime; 需要安装AspNetCoreModule托管模块:DotNetCore.2.0.5-WindowsHosting.exe ...

  7. python中类的输出或类的实例输出为何是<__main__类名 object at xxxx>这种形式?

    原因: __str__()这个特殊方法将对象转换为字符串的结果 效果图: 代码: # 定义一个Person类 class Person(object): """人类&qu ...

  8. JS中Cookie、localStorage、sessionStorage三者的区别

    cookie:大小4k,一般由服务器生成,可设置失效时间,关闭浏览器后失效,与服务器通信时:每次都会携带HTTP头中,如果使用cookie保存过多数据会带来性能问题 localhostStorage: ...

  9. 第五篇:python购物车小程序开发demo

    功能:自定义工资水平,可选商品加购余额实时提醒用到的知识点:列表.if多分支.循环.高亮输出未解决bug删除商品后不能自动退出 代码如下: if shopping_list: shopping_lis ...

  10. scope为provided

    以下面dependency为例 1 <dependency> 2 <groupId>javax.servlet</groupId> 3 <artifactId ...