Netty学习笔记通信调度篇:NioEventLoopGroup源码分析

目录

NioEventLoopGroup类层次结构

MultithreadEventExecutorGroup

EventExecutorChooser

FastThreadLocalRunnable


在创建主从Reactor多线程模型的Netty服务端时,服务端的代码如下:

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new SimpleServerHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                        }
                    });

            ChannelFuture f = b.bind(8888).sync();

            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

每个EventLoopGroup都是独立的Reactor线程池。

一个用于接收客户端的TCP连接,另一个用于处理IO相关的读写操作,或者执行系统Task,定时Task等。

用于接收客户端请求的线程池的职责如下:

  • 接收客户端TCP连接,初始化Channel参数;
  • 将链路状态变更事件通知给ChannelPipeline

用于处理IO操作的Reactor的线程池职责如下:

  • 一部读取通信对端的数据报,发送读事件到ChannelPipeline;
  • 异步发送消息到通信对端,调用ChannelPipeline的消息发送接口;
  • 执行系统调用Task;
  • 执行定时任务Task,例如链路空闲状态检测定时任务。

NioEventLoopGroup类层次结构

通过查源码发现Executor接口就是java.util.concurrent下的Executor下Doug Lea大神写的线程池框架。

NioEventLoopGroup核心的类继承关系就是:

NioEventLoopGroup –》MultithreadEventLoopGroup –》MultithreadEventExecutorGroup

看一下NioEventLoopGroup的源码,这些构造方法最终都会调用到父类MultithreadEventLoopGroup的构造方法:

//对于不指定线程数参数的构造器,默认设置0 (但是在后面的构造器中会判断,如果设置为0 就会初始化为2*CPU)
    public NioEventLoopGroup() {
        this(0);
    }

    /**
     * Create a new instance using the specified number of threads, {@link ThreadFactory} and the
     * {@link SelectorProvider} which is returned by {@link SelectorProvider#provider()}.
     */
//然后调用:这里设置了NioEventLoopGroup线程池中每个线程执行器默认是null(这里设置为null,在后面的构造器中会判断,如果为null就实例化一个线程执行器)
    public NioEventLoopGroup(int nThreads) {
        this(nThreads, (Executor) null);
    }
//再调用:这里就存在于JDK的NIO的交互了,这里设置了线程池的SelectorProvider, 通过SelectorProvider.provider() 返回。
    public NioEventLoopGroup(int nThreads, Executor executor) {
        this(nThreads, executor, SelectorProvider.provider());
    }
//然后调用:在这个重载的构造器中又传入了默认的选择策略工厂DefaultSelectStrategyFactory;
    public NioEventLoopGroup(
            int nThreads, Executor executor, final SelectorProvider selectorProvider) {
        this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
    }
//这里就是调用父类MultithreadEventLoopGroup的构造器了, 这里还添加了线程的拒绝执行策略。
    public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,final SelectStrategyFactory selectStrategyFactory) {
        super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
    }

这些构造方法可以指定5种参数:

  • 最大线程数量。如果指定为0,那么Netty会将线程数量设置为CPU逻辑处理器数量的2倍
  • 线程工厂。要求线程工厂类必须实现java.util.concurrent.ThreadFactory接口。如果没有指定线程工厂,那么默认设置为DefaultThreadFactory。
  • SelectorProvider。如果没有指定SelectorProvider,那么默认的SelectorProvider为SelectorProvider.provider()。
  • SelectStrategyFactory。如果没有指定则默认为DefaultSelectStrategyFactory.INSTANCE
  • RejectedExecutionHandler。类似于JDK线程池,如果这个EventLoopGroup已被关闭,那么之后提交的Runnable任务会默认调用RejectedExecutionHandler的reject方法进行处理。如果没有指定,则默认指定为RejectedExecutionHandlers.REJECT(直接抛出RejectedExecutionException异常)

MultithreadEventExecutorGroup

MultithreadEventExecutorGroup继承AbstractEventExecutorGroup的子类,而此类做了对线程的大多的实现,从名字可以看出他是多线程事件执行组,而netty是事件驱动的所以在很多定义里都有这个event事件做了标注,以下是他源码。

//可以看出他也是一个抽象类说明它内部有一些抽象方法需要子类实现去定制一些特制的功能。
public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
    //执行器数组这个EventExecutor是group管理的执行器,在next方法可以看到他是返回的此执行器
    //采用了final修饰并且采用了数组说明他的长度是固定的
    //需要注意固定的修饰因为后面再使用的时候会有引用
    private final EventExecutor[] children;
    //此set是对上方的执行器数组的一个副本,并且这个副本只读。
    private final Set<EventExecutor> readonlyChildren;
    //中断执行器的数量,如果group被中断则会遍历调用children的中断方法,而每个children被中断都会进行一个计数
    //而terminatedChildren则是对中断children的计数,为何使用后面再中断将会讲述
    private final AtomicInteger terminatedChildren = new AtomicInteger();
    //中断执行的返回结果,因为需要关闭时一个执行组所以为了异步执行所以返回了一个应答然后根据用于调用去决定是等待获取结果
    //还是去设置一个结果事件,之前在讲述future的时候详细介绍过,等下讲述的时候将会详细介绍他的使用
    private final Promise<?> terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
    //执行的选择器,什么是选择器呢,因为是线程组那么在来任务的时候将会选择使用哪个执行器去执行这个任务
    //而此选择器则用到了,之前我们看到的定义next方法其实他的实现就是使用了这个选择器去返回执行器
    //具体使用讲到的地方会详细说明
    private final EventExecutorChooserFactory.EventExecutorChooser chooser;
    //线程池(线程执行组)的构造器
    //nThreads 之前说过children是限制长度的而此参数就是用来设置此线程池的线程数大小
    //threadFactory 线程的创建工厂,用于创建线程
    //args 在创建执行器的时候传入固定参数,使用时将会讲述
    protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
        //这里有个小逻辑如果传入的线程工厂不是null则把工厂包装给一个executor。如果默认传null则会用默认的线程工厂
        this(nThreads, threadFactory == null ? null : new ThreadPerTaskExecutor(threadFactory), args);
    }

    //除了传入线程工厂还有一个做法就是传入一个executor,上一个构造就是对此构造的封装
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
        //传入了默认的执行器的选择器
        this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
    }
    /**
 * 最终的创建实例构造器
 *
 * @param nThreads          该实例将使用的线程数
 * @param executor          将要使用的executor, 默认为null
 * @param chooserFactory    将要使用的EventExecutorChooserFactory
 * @param args              arguments which will passed to each {@link #newChild(Executor, Object...)} call
 */
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                        EventExecutorChooserFactory chooserFactory, Object... args) {
    /** 1.初始化线程池 */
    //参数校验nThread合法性,
    if (nThreads <= 0) {
        throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
    }
	//executor校验非空, 如果为空就创建ThreadPerTaskExecutor, 该类实现了 Executor接口
    //这个executor 是用来执行线程池中的所有的线程,也就是所有的NioEventLoop,其实从
    //NioEventLoop构造器中也可以知道,NioEventLoop构造器中都传入了executor这个参数。
    if (executor == null) {
        executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }
	//这里的children数组, 其实就是线程池的核心实现,线程池中就是通过指定的线程数组来实现线程池;
    //数组中每个元素其实就是一个EventLoop,EventLoop是EventExecutor的子接口。
    children = new EventExecutor[nThreads];

	//for循环实例化children数组,NioEventLoop对象
    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {
        	//newChild(executor, args) 函数在NioEventLoopGroup类中实现了, 
            // 实质就是就是存入了一个 NIOEventLoop类实例
            children[i] = newChild(executor, args);
            success = true;
        } catch (Exception e) {
            // TODO: Think about if this is a good exception type
            throw new IllegalStateException("failed to create a child event loop", e);
        } finally {
         	//如果构造失败, 就清理资源
            if (!success) {
                for (int j = 0; j < i; j ++) {
                    children[j].shutdownGracefully();
                }

                for (int j = 0; j < i; j ++) {
                    EventExecutor e = children[j];
                    try {
                        while (!e.isTerminated()) {
                            e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                        }
                    } catch (InterruptedException interrupted) {
                        // Let the caller handle the interruption.
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }
    }
	// 2.实例化线程工厂执行器选择器: 根据children获取选择器 
    chooser = chooserFactory.newChooser(children);
	// 3.为每个EventLoop线程添加 线程终止监听器
    final FutureListener<Object> terminationListener = new FutureListener<Object>() {
        @Override
        public void operationComplete(Future<Object> future) throws Exception {
            if (terminatedChildren.incrementAndGet() == children.length) {
                terminationFuture.setSuccess(null);
            }
        }
    };

    for (EventExecutor e: children) {
        e.terminationFuture().addListener(terminationListener);
    }
	// 4. 将children 添加到对应的set集合中去重, 表示只可读。
    Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
    Collections.addAll(childrenSet, children);//(EventLoop是EventExecutor的子接口)
    readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
    //获取默认的线程工厂并且传入当前类名
    protected ThreadFactory newDefaultThreadFactory() {
        return new DefaultThreadFactory(getClass());
    }
    //next则是使用了选择器的next方法
    @Override
    public EventExecutor next() {
        return chooser.next();
    }
    //迭代执行器的时候调用的试只读的set
    @Override
    public Iterator<EventExecutor> iterator() {
        return readonlyChildren.iterator();
    }

    //获取当前执行器的数量
    public final int executorCount() {
        return children.length;
    }
    //声明了一个创建执行器的方法并且抽象的,因为每个执行器的实现都有特殊的操作所以此处抽象
    protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
    //之前说过调用线程组的关闭其实就是遍历执行器集合的关闭方法因为之前加了监听器去处理返回结果所以此处返回的future用于监听是否执行结束了
    @Override
    public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
        for (EventExecutor l: children) {
            l.shutdownGracefully(quietPeriod, timeout, unit);
        }
        return terminationFuture();
    }
    //获取终止结果
    @Override
    public Future<?> terminationFuture() {
        return terminationFuture;
    }
    //与关闭相同只不过此方法无返回值并且调用的方法不同是执行器的shutdown
    @Override
    @Deprecated
    public void shutdown() {
        for (EventExecutor l: children) {
            l.shutdown();
        }
    }
    //上方方法相同
    //这里要注意只有所有的执行器都是关闭中状态才会是true
    @Override
    public boolean isShuttingDown() {
        for (EventExecutor l: children) {
            if (!l.isShuttingDown()) {
                return false;
            }
        }
        return true;
    }
    //上方是关闭中这里是关闭
    @Override
    public boolean isShutdown() {
        for (EventExecutor l: children) {
            if (!l.isShutdown()) {
                return false;
            }
        }
        return true;
    }
    //是否终止
    @Override
    public boolean isTerminated() {
        for (EventExecutor l: children) {
            if (!l.isTerminated()) {
                return false;
            }
        }
        return true;
    }
    //等待时间范围是否执行终止完成
    @Override
    public boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException {
                //计算死线时间
        long deadline = System.nanoTime() + unit.toNanos(timeout);
        //遍历执行器
        loop: for (EventExecutor l: children) {
            //死循环以便使用死线时间
            for (;;) {
                //如果当前的时间是大于死线时间则会小于等于0
                long timeLeft = deadline - System.nanoTime();
                //如果小于等于0则跳出loop就是最外层循环,不在循环
                if (timeLeft <= 0) {
                    break loop;
                }
                //否则当前的线程等待计算的时间如果在时间内终止则跳出循环再次遍历下一个执行器然后计算时间再次重复操作
                if (l.awaitTermination(timeLeft, TimeUnit.NANOSECONDS)) {
                    break;
                }
            }
        }
        //如果到了时间则获取当前的终止结果
        return isTerminated();
    }
}

总结一下上面的一些重点:

1)构造children数组,长度为nThreads

在NioEventLoopGroup中,这个数组用于存储NioEventLoop实例。
构造完成后,随即会构造一个EventExecutorChooser实现类。EventExecutorChooser用于MultithreadEventExecutorGroup的next方法实现,当提交一个Runnable任务时,就会调用next方法从children数组中取出一个EventExecutor(NioEventLoop),并将这个任务交给这个EventExecutor所属的线程执行。

2)遍历children数组,为每个数组位构造一个NioEventLoop:

所有的NIOEventLoop线程是使用相同的 executor、SelectorProvider、SelectStrategyFactory、RejectedExecutionHandler以及是属于某一个NIOEventLoopGroup的。 这一点从 newChild(executor, args); 方法就可以看出:newChild()的实现是在NIOEventLoop中实现的。

    @Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
    }

3)构造一个FutureListener监听器,并添加到每个children数组持有的EventExecutor实例中
这个监听器会在EventLoop被关闭后得到通知

4)初始化EventExecutorChooser

当有IO事件来时,需要从线程池中选择一个线程出来执行,这时候的NioEventLoop选择策略是由GenericEventExecutorChooser实现的, 并调用该类的next() 方法获取到下一个 NioEventLoop.

EventExecutorChooser

//工厂的定义本简单定义了一个创建选择器的方法newChooser 然后定义了内部接口EventExecutorChooser只有一个方法next
public interface EventExecutorChooserFactory {

    EventExecutorChooser newChooser(EventExecutor[] executors);

    interface EventExecutorChooser {
        EventExecutor next();
    }
}
private interface EventExecutorChooser {
        EventExecutor next();
    }
//当初如线程是2的平方时,用这个选择
    private final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
        @Override
        public EventExecutor next() {
            return children[childIndex.getAndIncrement() & children.length - 1];
        }
    }
//除此之前的其他的线程选择
    private final class GenericEventExecutorChooser implements EventExecutorChooser {
        @Override
        public EventExecutor next() {
            return children[Math.abs(childIndex.getAndIncrement() % children.length)];
        }
    }

发现以上2个方法最后得到的结果都是一样的,按顺序选择children数组中的线程,不同之外就是如果是2的平方的形式,因为是按位运算,所以执行的效率上是非常快的。而其他的选择因为要进行除余运算,所以在效率上应该会差一点。ThreadPerTaskExecutor 

//此类是用于包装了线程工厂的一个执行器
//这里需要对此类有印象后面再讲述执行器实现的时候回使用到他。
//这个类很简单他的类名很清楚的讲述了,每个任务的执行线程,代表后面的执行器的实现都不会有线程的操作都是有此类进行的,这里要有印象
public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        if (threadFactory == null) {
            throw new NullPointerException("threadFactory");
        }
        this.threadFactory = threadFactory;
    }
    //当调用此执行器时将会使用线程工厂创建一个线程去执行传入的runnable
    @Override
    public void execute(Runnable command) {
        threadFactory.newThread(command).start();
    }
}
//默认的线程工厂
public class DefaultThreadFactory implements ThreadFactory {
    //线程组的id,这里组代表是工厂,因为工厂是可以new的如果不分配到时候很难看出是哪里个工厂创建的线程
    //而此处采用了static代表此属性是跟类走的而不是对象所以每次创建一个工厂pool都会增加一
    private static final AtomicInteger poolId = new AtomicInteger();
    //创建线程的自增id
    private final AtomicInteger nextId = new AtomicInteger();
    //线程名前缀
    private final String prefix;
    //是否是守护线程
    private final boolean daemon;
    //当前线程的优先级
    private final int priority;
    //创建线程所属的线程组,可以为null系统会使用默认的线程组
    protected final ThreadGroup threadGroup;
    //下面是线程工厂的构造这里统一说明一下
    //poolType 是Class 类型他最终会被转换成类名用于poolName的使用
    //poolName 线程名但是不是完整的他会拼接一些其他数据比如poolId
    //daemon 是否为守护线程除非手动设置否则默认都是false
    //priority 线程的优先级 默认是NORM_PRIORITY 也是系统默认的
    public DefaultThreadFactory(Class<?> poolType) {
        this(poolType, false, Thread.NORM_PRIORITY);
    }

    public DefaultThreadFactory(String poolName) {
        this(poolName, false, Thread.NORM_PRIORITY);
    }

    public DefaultThreadFactory(Class<?> poolType, boolean daemon) {
        this(poolType, daemon, Thread.NORM_PRIORITY);
    }

    public DefaultThreadFactory(String poolName, boolean daemon) {
        this(poolName, daemon, Thread.NORM_PRIORITY);
    }

    public DefaultThreadFactory(Class<?> poolType, int priority) {
        this(poolType, false, priority);
    }

    public DefaultThreadFactory(String poolName, int priority) {
        this(poolName, false, priority);
    }

    public DefaultThreadFactory(Class<?> poolType, boolean daemon, int priority) {
        this(toPoolName(poolType), daemon, priority);
    }
    //此方法是将Class类型的poolType获取类名作用于线程名
    public static String toPoolName(Class<?> poolType) {
        if (poolType == null) {
            throw new NullPointerException("poolType");
        }
        //根据Class获取类名
        String poolName = StringUtil.simpleClassName(poolType);
        //这里判断他的长度如果是0个长度则返回unknown如果一个长度则将它最小化然后返回
        //如果大于一个长度则判断第一个字符是不是大写第二个字符是不是小写日过是则吧第一个字符小写拼接上后面的字符返回
        //否则直接返回获取到的类名
        switch (poolName.length()) {
            case 0:
                return "unknown";
            case 1:
                return poolName.toLowerCase(Locale.US);
            default:
                if (Character.isUpperCase(poolName.charAt(0)) && Character.isLowerCase(poolName.charAt(1))) {
                    return Character.toLowerCase(poolName.charAt(0)) + poolName.substring(1);
                } else {
                    return poolName;
                }
        }
    }
    //上面遗漏了一个参数threadGroup 这个参数是线程组可以为null在这里也并没有任何使用的意义,都在创建线程后的线程设置
    public DefaultThreadFactory(String poolName, boolean daemon, int priority, ThreadGroup threadGroup) {
        if (poolName == null) {
            throw new NullPointerException("poolName");
        }

        //优先级需要合法否则抛出异常
        if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
            throw new IllegalArgumentException(
                    "priority: " + priority + " (expected: Thread.MIN_PRIORITY <= priority <= Thread.MAX_PRIORITY)");
        }
        //这里就是拼接线程前缀的地方,刚才处理的名字加上工厂组的id
        prefix = poolName + '-' + poolId.incrementAndGet() + '-';
        //下面就是一些属性的赋值没什么特殊意义
        this.daemon = daemon;
        this.priority = priority;
        this.threadGroup = threadGroup;
    }
    //将线程创建的group逻辑在这里操作了一遍,为了防止组为null之前说过可以传入null因为系统会自动设置而系统设置方式和此处一样
    public DefaultThreadFactory(String poolName, boolean daemon, int priority) {
        this(poolName, daemon, priority, System.getSecurityManager() == null ?
                Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup());
    }
    //创建线程
    @Override
    public Thread newThread(Runnable r) {
        //这里可以看出它使用了一个静态方法做了包装runnable 然后使用前面工厂的前缀名拼接了线程的id号
        Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
        try {
            //如果创建的线程与工厂不一致比如工厂设置的守护线程工厂daemon是true那么创建的线程是false将会进行设置
            if (t.isDaemon() != daemon) {
                t.setDaemon(daemon);
            }
            //优先级与上方一样
            if (t.getPriority() != priority) {
                t.setPriority(priority);
            }
        } catch (Exception ignored) {
            // Doesn't matter even if failed to set.
        }
        return t;
    }
    //实际上就是创建了一个FastThreadLocalThread线程的子类
    protected Thread newThread(Runnable r, String name) {
        return new FastThreadLocalThread(threadGroup, r, name);
    }
}

FastThreadLocalRunnable

Netty中FastThreadLocal的专用线程

//上方对runnable的包装,并没有的什么可讲的
final class FastThreadLocalRunnable implements Runnable {
    private final Runnable runnable;

    private FastThreadLocalRunnable(Runnable runnable) {
        this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
    }

    @Override
    public void run() {
        try {
            runnable.run();
        } finally {
            FastThreadLocal.removeAll();
        }
    }

    static Runnable wrap(Runnable runnable) {
        return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值