zoukankan      html  css  js  c++  java
  • Netty源码解析之EventLoopGroup

    背景参考

    线程之ExecutorService

    Reactor

    • 首先,搞懂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操作进行处理

  • 相关阅读:
    利用Session和HashTable制作购物车实例
    在windows 7上安装Maven2.2.1
    tail & cut 命令
    软件开发常用名词中英文对照
    字符,字节和编码
    grep 简介
    HSQLDB: java程序使用hsqldb 入门教程 java启动hsqldb
    eclipse 安装 maven 插件
    JNI 返回结构体参数
    HSQLDB 安装与使用
  • 原文地址:https://www.cnblogs.com/zby9527/p/13206654.html
Copyright © 2011-2022 走看看