目录
背景参考
- 首先,搞懂JDK线程池
- 再熟练掌握reactor模式
- 最后再来理解JDK的线程模型
Future扩展
Future
- 继承JDK的Future,提供更多状态方法,额外引入事件监听
- 监听在操作完成后自动触发
- 异步获取执行结果
Promise
- 可写的Future
- Future实际上是等待任务执行结束后写入结果,然后唤醒等待线程;Promise提供写方法,直接可以写入结果,唤醒等待线程。
Netty之EventExecutorGroup
概览
EventExecutorGroup和EventExecutor?
- EventExecutor继承EventExecutorGroup,但是EventExecutorGroup的next()方法又返回EventExecutor对象。这里就陷入一个死循环了,先有鸡(EventExecutorGroup)还是先有蛋(EventExecutor)?这个地方其实是违反了依赖倒转设计原则的,父子接口互相依赖,因此这个地方很难理解。
public interface EventExecutorGroup extends ScheduledExecutorService, Iterable<EventExecutor> {
//省略其他方法
EventExecutor next();
@Override
Iterator<EventExecutor> iterator();
}
public interface EventExecutor extends EventExecutorGroup {
//省略其他方法
@Override
EventExecutor next();
EventExecutorGroup parent();
boolean inEventLoop();
boolean inEventLoop(Thread thread);
-
我们在使用JDK线程池时,任务之间其实是独立的,我们并不关心他们的执行先后顺序,因为我们的任务都是原子的,因此我们并不关心任务被哪个线程拿到执行。所以,JDK的线程之并没有给我们提供获取内部线程的方法,我们也不需要。
-
首先,我们从Netty的线程模型入手,分析这里的设计原因:在Netty中,我们的一个Channel是绑定到一个线程上的,也就是一个Channel绑定到一个线程上,一个线程可以绑定多个Channel。绑定的好处是一个Channel上的所有操作都是串行的,因为只有一个线程处理这个Channel。如果是直接把Channel的操作提交给线程池,那么可能读写等操作乱序,需要额外的机制保证并发,这种绑定避免了这些额外开销。
-
对比JDK和Netty的线程池实现,总结如下:JDK线程池提交的任务是独立的,Netty提交的任务是需要保证执行顺序的。
-
从第三点我们知道,Netty中,我们需要把Channel绑定到一个特定的线程上去,因此我们需要获取到线程池的某个线程,并且这个线程可以当成一个线程池来使用,我们可以向这个线程提交任务。EventExecutor也提供了inEventLoop方法用户判断当前代码执行是不是在绑定的线程,如果不是,我们就需要通过提交任务的方式提交,如果是,我们就可以直接执行,因此我们可以看到很多类似代码
//获取绑定的线程
EventLoop eventLoop = eventLoop();
//如果当前线程是绑定的线程,直接执行
if (eventLoop.inEventLoop()) {
setReadPending0(readPending);
//否则,提交到绑定线程中执行
} else {
eventLoop.execute(new Runnable() {
@Override
public void run() {
setReadPending0(readPending);
}
});
}
AbstractEventExecutorGroup
- 同理,既然EventExecutor继承EventExecutorGroup,那么EventExecutorGroup其实也不需要具体实现业务逻辑,委托给EventExecutor具体实现即可,因此AbstractEventExecutorGroup中都是
@Override
public Future<?> submit(Runnable task) {
return next().submit(task);
}
MultithreadEventExecutorGroup
- 维护一组EventExecutor,使用EventExecutorChooser进行选择
- 子类负责创建具体的EventExecutor实现
DefaultEventExecutorGroup
- 默认的事件执行器组
- 创建DefaultEventExecutor作为具体任务执行器
DefaultEventExecutor
- 维护一个线程,循环执行提交给它的任务
@Override
protected void run() {
for (;;) {
Runnable task = takeTask();
if (task != null) {
task.run();
updateLastExecutionTime();
}
if (confirmShutdown()) {
break;
}
}
}
DEMO
public static void main(String[] args) {
EventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(5);
eventExecutorGroup.execute(() -> System.out.println(Thread.currentThread().getName()));
eventExecutorGroup.execute(() -> System.out.println(Thread.currentThread().getName()));
eventExecutorGroup.execute(() -> System.out.println(Thread.currentThread().getName()));
eventExecutorGroup.execute(() -> System.out.println(Thread.currentThread().getName()));
EventExecutor eventExecutor = eventExecutorGroup.next();
eventExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
eventExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
eventExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
eventExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
}
defaultEventExecutorGroup-2-1
defaultEventExecutorGroup-2-2
defaultEventExecutorGroup-2-3
defaultEventExecutorGroup-2-4
defaultEventExecutorGroup-2-5
defaultEventExecutorGroup-2-5
defaultEventExecutorGroup-2-5
defaultEventExecutorGroup-2-5
- 前四次是提交给EventExecutorGroup的,因此给不同线程执行的
- 后四次是提交给一个指定的eventExecutor的,所以由这个执行器所在线程执行,后面四个任务顺序执行
- 需要执行顺序任务,就可以使用Netty提供的线程池,提交到同一个eventExecutor
总结
- EventExecutorGroup就是Netty的线程池,对Future进行扩展,添加更丰富的操作,同时可以以监听的形式处理任务完成操作,避免无效get等待
- EventExecutorGroup可以获取单个的EventExecutor,提交给单个EventExecutor的任务串行执行
- 提交给EventExecutorGroup的任务轮询选择EventExecutor提交执行
Netty之EventLoopGroup
- 同理EventLoopGroup和EventLoop的关系跟EventExecutorGroup和EventExecutor的关系是一样的
- EventLoopGroup继承EventExecutorGroup,因此也提供了线程池功能
- 提供了额外注册Channel的方法
public interface EventLoopGroup extends EventExecutorGroup {
@Override
EventLoop next();
ChannelFuture register(Channel channel);
ChannelFuture register(ChannelPromise promise);
@Deprecated
ChannelFuture register(Channel channel, ChannelPromise promise);
}
- 从上面我们知道,Netty提供了一个Channel绑定到一个线程,一个线程可以绑定多个Channel,因此EventLoopGroup主要是实现这个概念的
- 同样,EventLoopGroup是不干事的,它的实现就是把调用交给EventLoop完成
NioEventLoop
- NIO常用的EventLoop实现
- 从继承结构可以知道,这是一个线程池的单线程实现,它也是EventExecutor子类,所以包含前面提到的功能,同时,他额外提供了Channel注册的功能
Channel注册到EventLoop上有什么用呢?
- EventExecutor本质是维护一个线程,然后run方法里面死循环执行任务
- EventLoop继承EventExecutor,在run方法里进行额外操作
- 以NioEventLoop为例,它包含一个成员变量Selector,这是NIO提供的,Channel注册实际上是注册到Selector上,在NioEventLoop的run方法里面,它的功能分为两部分:处理Selector的io事件、执行提交的任务。同时,我们还可以通过ioRatio字段控制两部分功能执行时间占比。
- 也就是一个Channel注册到EventLoop后,它就注册到了EventLoop的Selector上了,EventLoop的线程就会对Selector的io事件进行处理了。
总结
EventLoopGroup bossGroup = new NioEventLoopGroup();
- 上面代码实际创建了一个线程池
- 这个线程池可以获取实际执行任务的每一个EventLoop
- 每个EventLoop都维护一个线程,可以不断串行执行提交的任务
- NioEventLoopGroup维护了一个Selector变量,绑定channel实际上就是把NIO的Channel注册到Selector上
- EventLoop的run方法负责两部分功能:1)处理Selector上的io时间,2)处理提交给它的任务
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
new ServerBootstrap().group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new FixedLengthFrameDecoder(3))
.localAddress(8888)
.bind()
.sync()
.channel()
.closeFuture()
.sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
- 上面这个代码,其实就是创建了两个线程池EventLoopGroup,指定了底层通道实现NioServerSocketChannel
- NioServerSocketChannel实例化对象后,会注册到bossGroup上;新开启的NioSocketChannel会注册到
workerGroup上,这两个线程池对Selector的io操作进行处理