上一章讲了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. Netsharp总体介绍

    作者:秋时   日期:2014年02月05日   转载须说明出处  Netsharp交流群:338963050(请有详细的请求说明) Netsharp系列文章目录结构 Netsharp是一款免费的基于 ...

  2. Java:Hashtable

    概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习.我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable.第1部分 Ha ...

  3. DOM追加笔记

    根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点: 整个文档是一个文档节点 每个 HTML 元素是元素节点 HTML 元素内的文本是文本节点 每个 HTML 属性是属性节点 ...

  4. LOJ-10106(有向图欧拉回路的判断)

    题目链接:传送门 思路: (1)将每个单词视为有向路径,单词的起始字母是起始节点,末尾字母是终止节点,然后找由字母建立的有向图 是否是欧拉图或者半欧拉图. (2)先用并查集判断是否连通,再判断入度与出 ...

  5. 【转】Linux中的EAGAIN含义

    在Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中). 从字面上来看,是提示再试一次.这个错误经常出现在当应用程序进行一些非阻塞 ...

  6. python基本数据类型之字符串(四)

    python基本数据类型之字符串(四) 判断方法 python中有一类用来判断字符串形式的方法,该类方法有两个特点:(1)方法名都是is开头(除了startswith和endswith):(2)返回值 ...

  7. 如何搭建zabbix server端

    1.背景介绍: nginx:1.9.3 安装路径/data/nginxphp:5.5.27 安装路径 /data/phpmysql:5.6.28 安装路径/usr/local/mysqlzabbix ...

  8. shell解析my.cnf配置文件

    my.cnf配置格式如下 vi my.cnf[client]port=3306socket=/tmp/mysql.socket [mysqld]port=3306server-id=1datadir= ...

  9. getResource()和getResourceAsStream以及路径问题

    用JAVA获取文件,听似简单,但对于很多像我这样的新人来说,还是掌握颇浅,用起来感觉颇深,大常最经常用的,就是用JAVA的File类,如要取得c:/test.txt文件,就会这样用File file ...

  10. kali安装配置ftp

    参考:https://zhidao.baidu.com/question/1511146077646448900.html 一)安装 1.用sudo apt-get install 下载安装包