不管是在客户端还是服务端,在netty启动的时候,都会指定EventLoopGroup,当然用的最多的就是NIO,所以都会指定NioEventLoopGroup。那么这个NioEventLoopGroup在Nnetty中是扮演什么角色的喃!netty是基于Reactor模型的一个实现,那么我们先简单的说明一下Reactor的线程模型。
单线程模型:
所有的acceptor处理和handler处理都在同一个线程中。这种模式的坏处就是显而易见的了,当一个handler阻塞时,会导致所有的handler都不能执行,同时整个服务也不会接受新请求。
多线程模型:
与单线程模型的区别在于,把acceptor让一个独立的线程来处理,后面有一组NIO的线程池来处理客户端连接的IO操作。
特点:
有一个专门的acceptor线程用于监听客户端的TCP连接请求。
客户端连接的IO操作由一个特定的NIO线程池负责. 每个客户端连接都与一个特定的NIO线程绑定, 因此在这个客户端连接中的所有IO操作都是在同一个线程中完成的.
客户端连接有很多,但是NIO线程池中的线程数是较少的,因此一个NIO线程可以同时绑定到多个客户端连接中。
为什么不让一个特定的线程池来处理acceptor喃???
单线程对应在netty中的表现是什么样子的喃
看一个例子:
EventLoopGroup bossGroup = new NioEventLoopGroup(1); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup) .channel(NioServerSocketChannel.class) ........
NioEventLoopGroup构造参数是1,表明线程池的大小是1,接着调用ServerBootstrap的group(bossGroup)方法。接着我们看看ServerBootstrap的group(bossGroup)方法的实现:
public ServerBootstrap group(EventLoopGroup group) { return group(group, group); }
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { super.group(parentGroup); if (childGroup == null) { throw new NullPointerException("childGroup"); } if (this.childGroup != null) { throw new IllegalStateException("childGroup set already"); } this.childGroup = childGroup; return this; }
很容易我们看出来了,传入一个group的时候,我们的bossGroup和workerGroup是同一个NioEventLoopGroup。并且这个NioEventLoopGroup只有一个线程,这样acceptor和后续的所有客户端连接的IO操作都在这个线程里面了,这个就是对应Reactor的单线程模型。
再看一个例子:
EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) ........
bossGroup只有一个线程,workerGroup的线程是CPU核心数乘以2。这样就是Reactor的多线程模式。
下面来看看NioEventLoopGroup和NioEventLoop初始化过程:
1,NioEventLoopGroup内部维护一个类型为EventExecutor数组(变量:children),构造了一个线程池
2,调用newChild抽象方法来初始化children数组
3,抽象方法newChild在 NioEventLoopGroup 中实现的,返回一个NioEventLoop实例
NioEventLoop:
继承关系:NioEventLoop-->SingleThreadEventLoop-->SingleThreadEventExecutor-->AbstractScheduledEventExecutor。 SingleThreadEventExecutor是netty中对本地线程的抽象,它内部有一个Thread thread属性, 存储了一个本地Java线程。一个NioEventLoop和一个线程绑定.
在AbstractScheduledEventExecutor中,实现了schedule的功能,我们可以通过调用NioEventLoop实例的schedule方法来运行一些定时任务。
在SingleThreadEventLoop中实现了任务队列的功能,我们可以通过调用NioEventLoop实例的execute方法来向任务队列中添加一个task,由NioEventLoop进行调度执行。
NioEventLoop肩负着两种任务:第一作为IO线程,执行与channel的IO操作,包括调用select等待就绪的IO事件,读写数据,数据的处理。第二作为任务队列,执行队列中的任务
关于channel和EventLoop是怎么关联起来的可以看看
http://jishuaige.iteye.com/blog/2356798---netty探索之旅三
EventLoop的启动
NioEventLoop的启动,其实就是NioEventLoop所绑定的本地Java线程的启动。
在NioEventLoop的父类SingleThreadEventExecutor中
private void startThread() { if (STATE_UPDATER.get(this) == ST_NOT_STARTED) { if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) { thread.start(); } } }
这个方法调用NioEventLoop所绑定的本地Java线程的start方法,启动线程。
跟着这个方法被调用轨迹走:SingleThreadEventExecutor中的execute
public void execute(Runnable task) { if (task == null) { throw new NullPointerException("task"); } boolean inEventLoop = inEventLoop(); if (inEventLoop) { addTask(task); } else { startThread(); addTask(task); if (isShutdown() && removeTask(task)) { reject(); } } if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }
在第三节我们分析channel注册的时候,在AbstractUnsafe.register有这段代码:
if (eventLoop.inEventLoop()) { register0(promise); } else { try { eventLoop.execute(new Runnable() { @Override public void run() { register0(promise); } }); .....................
eventLoop.execute就是在调用上面的SingleThreadEventExecutor.execute,这样NioEventLoop所对应的线程就启动了。
当EventLoop.execute第一次被调用时,就会触发startThread()的调用,进而EventLoop所对应的Java线程的启动。