上一章讲了EventExecutorGroup的整体结构和原理,这一章我们来探究一下它的具体实现。
EventExecutorGroup和EventExecutor接口
io.netty.util.concurrent.EventExecutorGroup
java.util.concurrent.ScheduledExecutorService
EventExecutorGroup继承了ScheduledExecutorService接口,它自己定义了如下的新方法
方法
说明
EventExecutor next()
取出一个EventExecutor, 这个方法要实现派发任务的策略。
Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);
优雅地关闭这个executor, 一旦这个方法被调用,isShuttingDown()方法总是总是返回true。和 shutdown方法不同,这个方法需要确保在关闭的平静期(由quietPeriod参数决定)没有新的任务被提交,如果平静期有新任务提交,它会接受这个任务,同时中止关闭动作,等任务执行完毕后从新开始关闭流程。
Future<?> shutdownGracefully()
shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit)快捷调用方式。
boolean isShuttingDown()
检查是否已经调用了shutdownGracefully或shutdown方法。
io.netty.util.concurrent.EventExecutor implement EventExecutorGroup
EventExecutor定义的接口如下
方法
说明
boolean inEventLoop()
如果当前线程是这个Executor返回true
boolean inEventLoop(Thread thread)
如果thread是这个Executor的线程返回true
EventExecutorGroup parent()
返回持有这个Executor的EventExecutorGroup
<V> Promise<V> newPromise()
创建一个新的Promise实例
<V> ProgressivePromise<V> newProgressivePromise()
创建一个新的ProgressivePromise实例
<V> Future<V> newSucceededFuture(V result);
创建一个标记为success的Future实例,Future#isSuccess()返回true
<V> Future<V> newFailedFuture(Throwable cause)
创建一个标记为failed的Future实例,Future#isSuccess()返回false
抽象实现AbstractEventExecutorGroup和AbstractEventExecutor
io.netty.util.concurrent.AbstractEventExecutorGroup implement EventExecutorGroup
AbstractEventExecutorGroup实现了EventExecutorGroup接口,它实现方法的形式为:
XXX(){
next().XXX()
}
如:execute方法的实现为
public void execute(Runnable command) {
next().execute(command);
}
这里实现了EventExecutorGroup派发任务的方式,使用next方法取出一EventExecutor, 然后把任务提交给这个executor。其他提交认任务的方法实submit, schedule, scheduleAtFixedRate, scheduleWithFixedDelay, invokeAll, invokeAny都和这个类似。
io.netty.util.concurrent.AbstractEventExecutor extends AbstractExecutorService implements EventExecutor
形如newXXX的方法,直接new一个JDK提供的类型的实例返回, 如:
public <V> Promise<V> newPromise() {
return new DefaultPromise<V>(this);
}
sumbit方法是调用AbstractExecutorService的实现。
不支持schedule, scheduleAtFixedRate, scheduleWithFixedDelay方法,这几个方法都会抛出UnsupportedOperationException异常。
多线程实现MultithreadEventExecutorGroup和SingleThreadEventExecutor
io.netty.util.concurrent.MultithreadEventExecutorGroup extends AbstractEventExecutorGroup
MultithreadEventExecutorGroup 主要实现了一下两个方面的功能:
  1. EventExecutor管理: 创建, 结束SingleThreadEventExecutor,EventExecutor的数据是固定的,由传入的参数决定。
  2. 任务派发策略: 实现了EventExecutor选择器,next方法使选择器选中一个Executor。
这个类的核心功能都在它的构造方法中实现, 构造方法有三个参数:
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args).
nThreads: 线程数,即SingleThreadEventExecutor的数量,
threadFactory: 线程工程,传递给SingleThreadEventExecutor实例,SingleThreadEventExecutor使用它创建一个工作线程。
args: 传递给SingleThreadEventExecutor工作线程的参数。
构造方法主要干了两件事:
1. 创建SingleThreadEventExecutor
children = new SingleThreadEventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
children[i] = newChild(threadFactory, args);
}
它把创建的SingleThreadEventExecutor实例放在children属性中维护。 newChild是个抽象方法,需要子类实现。
2. 创建选择器
if (isPowerOfTwo(children.length)) {
chooser = new PowerOfTwoEventExecutorChooser();
} else {
chooser = new GenericEventExecutorChooser();
}
MultithreadEventExecutorGroup内部实现两种类型的选择器,PowerOfTwoEventExecutorChooser--chooserA, GenericEventExecutorChooser--chooserB, 当线程数是2^n时使用chooserA, 否则使用chooserB。选择器的实现使用了一点小技巧,从本质上讲,这两种选择器都是使用取模轮询的方式选择下一个executor, 不同的是当线程数(children的长度)为2^n时可以把取模运算优化成位运算,性能比位运算要好一些。下面是两个选择器的算法:
chooserA: children[childIndex.getAndIncrement() & children.length - 1], 当children.length == 2^n时,它等价于 children[Math.abs(childIndex.getAndIncrement() % children.length)
chooserB: children[Math.abs(childIndex.getAndIncrement() % children.length)
这里我们可以得出结论, nThreads尽量设置成2^n(2, 4, 8, 16, 32 ....), 这样性能会好一些。
io.netty.util.concurrent.SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor
派生关系
SingleThreadEventExecutor
AbstractScheduledEventExecutor
AbstractEventExecutor
SingleThreadEventExecutor实现了一个单线程的Executor, 它使用外部传进来的ThreadFactory实例创建一个唯一的线程,executor方法把任务放进taskQueue中,线程消费taskQueue中排队的任务。这个executor不仅要执行由executor提交的任务,还要执行由schedule方法提交定时任务和由invokeAll, invokeAny提交的批量任务。
除了任务呢排队,这类还实现了一个重要的功能--gracefulShutdown, 优雅地关闭。
下面来详细分析这些功能的实现。
状态:
ST_NOT_STARTED = 1: 初始状态,SingleThreadEventExecutor 实例被创建时处于这个状态,这个时候只是创建了一个线程,这个线程还没有运行。
ST_STARTED = 2: 运行状态,ST_NOT_STARTED时,提交的第一个任务会把它变成这个状态,线程已经开发运行。
ST_SHUTTING_DOWN = 3: 正在执行关闭操作。线程主循环run方法返回或抛出异常,或调用shutdownGracefully 都会变成这个状态。
ST_SHUTDOWN = 4: 已经关闭。调用shutdown会变成这个状态。
ST_TERMINATED = 5: 已经结束。这个是最终状态,ST_SHUTTING_DOWN和ST_SHUTDOWN 状态的过程执行完毕后会变成这个状态。
状态判定方法
是否处于SHUTTING_DOWN状态
public boolean isShuttingDown() {return state >= ST_SHUTTING_DOWN;}
是否处于SHUTDOWN状态
public boolean isShutDown() {return state >= ST_SHUT_DOWN;}
实时任务排队:
public方法execute, 是提供给用户提交实时任务的方法,它的调用栈如下:
execute
addTask
offerTask
taskQueue.offer
execute最终会调用taskQueue的offer方法把任务放到队列中排队,在此之前,如果检测到处于SHUTDOWN状态,就拒绝这个任务,或offer失败也会拒绝任务。
定时任务排队:
用户调用schedule把定时任务到scheduledTaskQueue队列中,这个队列是PriorityQueue类型的实例,他是一个优先级队列。在线程的主循环run中,会调用takeTask,taskTask会优先调用peekScheduledTask,看一看scheduledTaskQueue有没有定时任务,如果有就尝试把所有已经到时间的定时任务放到taskQueue中排队。
批量任务排队:
批量任务排队比较简单,只是简单地对invokeAll或invokeAny的tasks参数中的所有任务调用一次execute。
取出任务:
takeTask的主要功能是从taskQueue中取出任务,同时它还确保到期的定时任务能够及时地进入taskQueue中排队。这是一个比较重要的方法,我们来详细分析它的实现:
BlockingQueue<Runnable> taskQueue = (BlockingQueue<Runnable>) this.taskQueue;
for (;;) {
ScheduledFutureTask<?> scheduledTask = peekScheduledTask(); //先看看优先级队列中是否存在定时任务
if (scheduledTask == null) {
//如果没有定时任务,直接从taskQueue中取出一个任务返回
Runnable task = null;
try {
task = taskQueue.take();
if (task == WAKEUP_TASK) {
task = null;
}
} catch (InterruptedException e) {
// Ignore
}
return task;
} else {
//运行到这里表示有定时任务
long delayNanos = scheduledTask.delayNanos();
Runnable task = null;
if (delayNanos > 0) {
//没有到期的定时任务,
try {
task = taskQueue.poll(delayNanos, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
return null;
}
}
if (task == null) {
//有到期的定时任务,把所有优先级队列中到期的定时任务放入taskQueue中排队
fetchFromScheduledTaskQueue();
task = taskQueue.poll();
}
if (task != null) { //在有定时任务但taskQueue为空的时候,for循环会一直空转,直到有定时任务到期才会跳出
return task;
}
}
}
优雅地关闭:
优雅地关闭是这个类的重要的功能,所谓优雅是指在正在关闭之前要确保已经在taskQueue中排队的任务都能被执行,在关闭过程中,如果用户提交了一个任务,是否提交成功要有明确的反馈,如果一个任务被成功提交,就要确保他最终一定会被执行。
线程的主循环run方法返回的时候,就会触发优雅关闭的过程。run方法返回肯由多种原因引起:用户主动调用了shutdown或shutdownGracefully,run方法抛出异常。执行优雅关闭的过程在confirmShutdown方法中实现,执行这个过程的前提是:
确保当前处于SHUTTINGDOWN状态即状态值>=ST_SHUTTING_DOWN
if (!isShuttingDown()) {
return false;
}
确保这个方法在eventLoop线程中执行
if (!inEventLoop()) {
throw new IllegalStateException("must be invoked from an event loop");
}
然后才是优雅关闭的过程:
清除掉定时任务
cancelScheduledTasks();
如果是第一次尝试关闭,设置gracefulShutdownStartTime我当前时间
if (gracefulShutdownStartTime == 0) {
gracefulShutdownStartTime = ScheduledFutureTask.nanoTime();
}
把已在队列中排队的任务都执行掉。
if (runAllTasks() || runShutdownHooks()) {
检查当前状态,如果是关闭状态:>= ST_SHUTDOWN,已经关闭完成。
if (isShutdown()) {
return true;
}
如果gracefulShutdownQuietPeriod==0表示, 关闭过程没有安静期,现在可以立即结束。
if (gracefulShutdownQuietPeriod == 0) {
return true;
}
执行到这里,表示关闭过程还没结束,如果当前状态是SHUTTINGDOWN向taskQueue中添加一个WAKEUP_TASK, 唤醒在taskQueue阻塞的线程。
wakeup(true);
return false;
}
执行到这里表示,taskQueue已经是空的了,同时执行完了所有的的shutdown hook回调。如果现在已经是SHUTDOWN状态,或者这个关闭过程使用的时间已经超时,表示关闭过程已经完成了。
final long nanoTime = ScheduledFutureTask.nanoTime();
if (isShutdown() || nanoTime - gracefulShutdownStartTime > gracefulShutdownTimeout) {
return true;
}
如果这个方法本次执行的时间没有超过安静时间(gracefulShutdownQuietPeriod, 它的值是在调用shutdownGracefully时设置), 100ms之后从新执行关闭过程。
if (nanoTime - lastExecutionTime <= gracefulShutdownQuietPeriod) {
wakeup(true);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Ignore
}
return false;
}
return true;
业务线程的默认实现
public class DefaultEventExecutorGroup extends MultithreadEventExecutorGroup
final class DefaultEventExecutor extends SingleThreadEventExecutor
DefaultEventExecutorGroup没有对MultithreadEventExecutorGroup做任何扩展。
DefaultEventExecutor只是实现了run方法
@Override
protected void run() {
for (;;) {
Runnable task = takeTask();
if (task != null) {
task.run();
updateLastExecutionTime();
}
if (confirmShutdown()) {
break;
}
}
}
这个方法的实现表明,run方法只有在以下3中情况下跳出:
  1. 用户主动调用shutdown。
  2. 用户主动调用shutdownGracefully。
  3. 抛出异常。

netty源码解解析(4.0)-5 线程模型-EventExecutorGroup框架的更多相关文章

  1. netty源码解解析(4.0)-4 线程模型-概览

    netty线程体系概览 netty的高并发能力很大程度上由它的线程模型决定的,netty定义了两种类型的线程: I/O线程: EventLoop, EventLoopGroup.一个EventLoop ...

  2. netty源码解解析(4.0)-7 线程模型-IO线程EventLoopGroup和NIO实现(二)

    把NIO事件转换成对channel unsafe的调用或NioTask的调用 processSelectedKeys()方法是处理NIO事件的入口: private void processSelec ...

  3. netty源码解解析(4.0)-6 线程模型-IO线程EventLoopGroup和NIO实现(一)

    接口定义 io.netty.channel.EventLoopGroup extends EventExecutorGroup 方法 说明 ChannelFuture register(Channel ...

  4. netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架

    编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...

  5. netty源码解解析(4.0)-11 Channel NIO实现-概览

      结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...

  6. netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理

    事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...

  7. netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现

    io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ...

  8. netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端

    本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...

  9. netty源码解解析(4.0)-15 Channel NIO实现:写数据

    写数据是NIO Channel实现的另一个比较复杂的功能.每一个channel都有一个outboundBuffer,这是一个输出缓冲区.当调用channel的write方法写数据时,这个数据被一系列C ...

随机推荐

  1. CMD命令启动和关闭SQL服务

    1.开启:net start mssqlserver 2.关闭:net stop mssqlserver

  2. Netsharp平台工具常见问题(FAQ)

    1. 请问EntityId如何填? 回答:Netsharp中EntityId是经常需要输入的一个字段,因为Netsharp工具一般的源头是实体元数据,也就是一般常说的所谓模型驱动.所以很多工具都需要E ...

  3. ActiveMQ_2安装

    Linux安装 环境JDK7以上 gz文件拷贝到 /usr/local/目录下 解压 后缀为 .tar.gz的压缩包 进入解压后的文件夹 cd apache-activemq-x.xx.x/ cd b ...

  4. python_day1_程序交互

    程序交互 在编写程序过程中总会有程序与用户交互的场景出现,这里面提到python会使用一个方法:input 用法: 例如:请用户输入一个账号 input"Please input your ...

  5. JQuery对checkbox的操作

    对复选框组的全选.全不选.不全选,获取选中的复选框的值的操作 点击全选按钮,复选框组全部选中或者全部取消. 实现全选按钮和复选框组的联动,当复选框组中有一个没有被选中后,那么id=‘checkedAl ...

  6. jsp页面有一个注册form表单,传值的时候后台接收到的全部是null

    [页面上的传值元素一定要有name属性才可在后台接受到参数的值.切记!] 此处一定要注意,form表单里面的元素,比如input元素是否和后台的requset.getparameter();中的参数名 ...

  7. unigui日志

    unigui日志 uniGUI本身提供了日志功能,利用uniServerModule.ServerLogger来控制如何写日志: Enabled:是否写日志 Options:logIndyExcept ...

  8. Word中带圈数字

    写论文时常常要求输入带圈数字,先在Word中输入代码,选中代码后按Alt+X(然后再粘贴到Excel中) 符号 代码⓪ 24ea① 2460② 2461③ 2462④ 2463⑤ 2464⑥ 2465 ...

  9. MyBatis 源码分析 - 内置数据源

    1.简介 本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑.搞懂这些数据源的实现,可使大家对数据源有更深入的认识.同时在配置这些数据源时,也会更清楚每种属性的意义和用途.因此,如果大家想知其 ...

  10. php 从2维数组组合为四维数组分析(项目中前台侧边栏导航三级分类显示)

    foreach函数(循环函数)内嵌套循环函数时,当内层完全循环完后,才会向上一级循环 数组要注意问题 array_merge----合并一个或多个数组 将一个或多个数组的单元合并起来,一个数组中的值附 ...