Netty4.0源码解析:NioEventLoop/NioEventLoopGroup的初始化

一、引言

Netty程序在启动时,需要指定最少一个EventLoopGroup实例(服务端引导可以指定2个,客户端引导只能指定1个)。一般情况下我们指定的EventLoopGroup实现类都是NioEventLoopGroup。在了解NioEventLoopGroup的作用及其内部实现原理之前,我们先来复习下Reactor线程模型:

Reactor线程模型有三种类型:单线程模型、多线程模型和主从线程模型。

1、单线程模型
在这里插入图片描述

单线程模型中,所有的连接请求处理和连接后的事件处理都由一个线程处理。当某个handler阻塞时,会导致整个服务器无法接收新的连接,所以一般很少采用这种模型。

在Netty应用中,如果需要采用这种模型,只需要在ServerBootstrap引导过程中调用group方法绑定一个NioEventLoopGroup并将线程指定为1即可:

ServerBootstrap boot = new ServerBootstrap();
boot.group(new NioEventLoopGroup(1));

2、多线程模型
在这里插入图片描述

在多线程模型中,一个线程专门处理连接事件,一个线程池专门处理读写IO事件。当一个客户端发起连接时,由Acceptor线程建立这个连接,并将这个连接转交给线程池处理,由这个线程池管理连接并处理连接后的任务。
一般情况下,多线程模型已经能够胜任大部分需求了。

在Netty中采用这种模型需要调用group方法指定两个NioEventLoopGroup并将bossGroup的最大线程数设为1:

ServerBootstrap boot = new ServerBootstrap();
boot.group(new NioEventLoopGroup(1), new NioEventLoopGroup());

3、主从线程模型
在这里插入图片描述

很多博客提到了Netty采用了主从线程模型来处理连接,但实际上Netty并不存在主从线程模型。传统的主从线程模型是多个线程处理一个ServerSocketChannel的OP_ACCPET事件,并转发给Reactor处理。但是Netty的JDK ServerSocketChannel和EventLoop是一一对应的,并不存在多个EventLoop共享一个ServerSocketChannel,所以对于只需要绑定一个端口的应用程序而言,只需要指定bossGroup为new NioEventLoopGroup(1)就行了。

那可能很多朋友会问,既然只有1个线程,那我直接实例化NioEventLoop不就好了?Netty之所以采用EventLoopGroup为bossGroup而不是直接使用EventLoop,是因为这个EventLoopGroup(bossGroup)可以支持在多个引导程序间共享,而没必要每个引导过程都实例化一个EventLoopGroup作为bossGroup。
Stackoverflow也有人提出了这个问题,大家可以参考一下:
https://stackoverflow.com/questions/34275138/why-do-we-really-need-multiple-netty-boss-threads

如果一定需要多线程处理连接,可以让Netty绑定多个端口,然后采用反向代理服务器(如Nginx)通过负载均衡转发到这些端口。

NioEventLoopGroup和NioEventLoop的继承关系:
在这里插入图片描述
其中ThreadPerChannelEventLoopGroup和ThreadPerChannelEventLoop用于传统的阻塞IO。

阻塞IO在服务端中用的相对较少,所以这里我们只分析NioEventLoop和NioEventLoopGroup


二、NioEventLoopGroup和NioEventLoop的初始化

NioEventLoopGroup提供了6个public构造方法:

public NioEventLoopGroup() {
	this(0);
}

public NioEventLoopGroup(int nThreads) {
	this(nThreads, null);
}

public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
	this(nThreads, threadFactory, SelectorProvider.provider());
}

public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
    this(nThreads, threadFactory, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}

public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory,
        final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) {
    super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}

public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory,final SelectorProvider selectorProvider,
        final SelectStrategyFactory selectStrategyFactory, final RejectedExecutionHandler rejectedExecutionHandler) {
    super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, rejectedExecutionHandler);
}

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

这些构造方法最终都会调用到父类MultithreadEventLoopGroup的构造方法:

static {
	//默认线程数为当前机器逻辑处理器数量的2倍
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
    if (logger.isDebugEnabled()) {
        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
    }
}

protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
    super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}

这里继续调用父类MultithreadEventExecutorGroup的构造方法:

protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
    if (nThreads <= 0) {
        throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
    }
    //如果没有指定线程工厂,那么构造一个默认的线程工厂
    if (threadFactory == null) {
        threadFactory = newDefaultThreadFactory();
    }
	//构造一个存放NioEventLoop的数组
    children = new SingleThreadEventExecutor[nThreads];
    //如果指定的线程数量是2的幂次,那么采用位运算方式进行取模运算,否则采用传统的取模运算法取下一个NioEventLoop
    if (isPowerOfTwo(children.length)) {
        chooser = new PowerOfTwoEventExecutorChooser();
    } else {
        chooser = new GenericEventExecutorChooser();
    }
	//遍历children数组
    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {
        	//为每个数组位构造一个NioEventLoop
            children[i] = newChild(threadFactory, args);
            success = true;
        } catch (Exception e) {
            throw new IllegalStateException("failed to create a child event loop", e);
        } finally {
        	//如果构造过程抛出异常
            if (!success) {
            	//关闭数组中所有已经构造好的NioEventLoop
                for (int j = 0; j < i; j ++)
                    children[j].shutdownGracefully();
				//同步等待这些NioEventLoop关闭
                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) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }
    }
	//构造一个FutureListener
    final FutureListener<Object> terminationListener = new FutureListener<Object>() {
        @Override
        public void operationComplete(Future<Object> future) throws Exception {
            if (terminatedChildren.incrementAndGet() == children.length) {
                terminationFuture.setSuccess(null);
            }
        }
    };
	//添加监听器,在NioEventLoop被关闭时触发
    for (EventExecutor e: children) {
        e.terminationFuture().addListener(terminationListener);
    }
}
NioEventLoopGroup的构造可以分为以下几个步骤:

1、如果没有指定线程工厂(ThreadFactory),那么默认调用newDefaultThreadFactory方法来构造一个线程工厂:

protected ThreadFactory newDefaultThreadFactory() {
	return new DefaultThreadFactory(getClass());
}

这里调用了DefaultThreadFactory的构造方法,并传入了当前类对象:

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

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

public DefaultThreadFactory(String poolName, boolean daemon, int priority) {
    this(poolName, daemon, priority, System.getSecurityManager() == null ?
        Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup());
}

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)");
    }
	//设置线程名前缀
    prefix = poolName + '-' + poolId.incrementAndGet() + '-';
    this.daemon = daemon;
    this.priority = priority;
    this.threadGroup = threadGroup;
}

当调用new DefaultThreadFactory(getClass())时,DefaultThreadFactory默认将生产的线程的名称前缀设为类名+"-"+poolId.incrementAndGet()+"-",例如"NioEventLoopGroup-1-",线程优先级默认为5,并将其设为守护线程,所属的线程组为父线程组(如果设置了安全管理器,则设置为安全管理器持有的线程组)。

DefaultThreadFactory持有1个静态变量和5个实例变量:

修饰符变量名作用
private static final AtomicIntegerpoolId用于设置线程名称前缀,其意义为这个线程工厂的序列号
private final AtomicIntegernextId用于设置线程名称后缀,每生成一个线程就加1
private final booleandaemon是否将生产的线程设为后台线程
private final intpriority默认设置的线程优先级
protected final ThreadGroupthreadGroup生产的线程所属的线程组

上述变量都会在初始化过程中被赋值。
生产线程的newThread方法:

@Override
public Thread newThread(Runnable r) {
	//将其包装为FastThreadLocalRunnable,并调用newThread方法构造线程
    Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
    try {
        if (t.isDaemon() != daemon) //设置是否为守护线程
            t.setDaemon(daemon);
        if (t.getPriority() != priority) //设置线程优先级
            t.setPriority(priority);
    } catch (Exception ignored) { }
    return t;
}

protected Thread newThread(Runnable r, String name) {
	return new FastThreadLocalThread(threadGroup, r, name);
}

DefaultThreadFactory生产的线程实例都属于FastThreadLocalThread类,FastThreadLocalThread继承了Thread类,它持有两个实例变量:boolean cleanupFastThreadLocals和InternalThreadLocalMap threadLocalMap。这两个变量都是用于Netty的FastThreadLocal的实现的(类似于JDK里面的ThreadLocal)。

Netty中的FastThreadLocal的查找效率比JDK中的ThreadLocal效率更高,能够始终以O(1)的时间复杂度查找到对应变量的值。这里限于篇幅就不再讨论具体实现了,有兴趣的可以谷歌。

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

如果nThreads为2的幂次,那么会构造一个PowerOfTwoEventExecutorChooser:

private final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
    @Override
    public EventExecutor next() {
        return children[childIndex.getAndIncrement() & children.length - 1];
	}
}

如果不是2的幂次,则构造一个GenericEventExecutorChooser:

private final class GenericEventExecutorChooser implements EventExecutorChooser {
    @Override
    public EventExecutor next() {
        return children[Math.abs(childIndex.getAndIncrement() % children.length)];
    }
}

前者采用位运算来进行取模运算,效率更高(HashMap也采用这种机制定位桶),后者则采用传统的取模运算,效率相对较低。

3、遍历children数组,为每个数组位构造一个NioEventLoop:
MultithreadEventExecutorGroup的构造方法通过newChild方法来构造NioEventLoop:

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

NioEventLoop的构造方法:

NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider,
        SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
	super(parent, threadFactory, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
	if (selectorProvider == null)
	   throw new NullPointerException("selectorProvider");
	if (strategy == null)
	   throw new NullPointerException("selectStrategy");
	provider = selectorProvider;
	final SelectorTuple selectorTuple = openSelector();
	selector = selectorTuple.selector;
	unwrappedSelector = selectorTuple.unwrappedSelector;
	selectStrategy = strategy;
}

NioEventLoop构造过程相对比较复杂,首先它调用了父类SingleThreadEventLoop的构造方法:

protected SingleThreadEventLoop(EventLoopGroup parent, ThreadFactory threadFactory,
    	boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, threadFactory, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
}

SingleThreadEventLoop的构造方法继续调用父类SingleThreadEventExecutor的构造方法:

protected SingleThreadEventExecutor(EventExecutorGroup parent, ThreadFactory threadFactory, 
		boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
    if (threadFactory == null) {
        throw new NullPointerException("threadFactory");
    }
    //所属的NioEventLoopGroup
    this.parent = parent;
    this.addTaskWakesUp = addTaskWakesUp; //默认为false
    //通过线程工厂创建这个NioEventLoop对应的线程对象
    thread = threadFactory.newThread(new Runnable() {
        @Override public void run() {
            //省略线程执行逻辑代码
        }
    });
    //创建线程的属性对象(DefaultThreadProperties属于这个类的内部类)
    threadProperties = new DefaultThreadProperties(thread);
    //当这个EventLoop持有的尚未执行的任务超过这个数后,新提交的任务会被拒绝执行并转交给RejectedExecutionHandler
    this.maxPendingTasks = Math.max(16, maxPendingTasks);
    //构造一个任务队列
    taskQueue = newTaskQueue();
    rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}

回到NioEventLoop的构造方法中:
调用完父类构造方法后,接着将SelectorProvider赋给NioEventLoop的实例变量provider,然后调用openSelector()方法获取SelectorTuple实例:

private SelectorTuple openSelector() {
    final Selector unwrappedSelector;
    try {
    	//通过SelectorProvdier获取Selector对象
        unwrappedSelector = provider.openSelector();
    } catch (IOException e) {
        throw new ChannelException("failed to open a new selector", e);
    }

    if (DISABLE_KEYSET_OPTIMIZATION) {
        return new SelectorTuple(unwrappedSelector);
    }
    //创建一个存放SelectionKey的数组
    final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
    
    Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            try {
            	//加载SelectorImpl类,并返回这个类的Class对象
                return Class.forName("sun.nio.ch.SelectorImpl", false,
                		PlatformDependent.getSystemClassLoader());
            //如果加载失败,返回异常对象
            } catch (Throwable cause) {
                return cause;
            }
        }
    });
    //如果返回的是异常类型(无法加载类),或者不是unwrappedSelector所属类的父类
    if (!(maybeSelectorImplClass instanceof Class) ||
            !((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
        if (maybeSelectorImplClass instanceof Throwable) {
            Throwable t = (Throwable) maybeSelectorImplClass;
            logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
        }
        return new SelectorTuple(unwrappedSelector);
    }
    //到这里说明sun.nio.ch.SelectorImpl已经加载成功
    final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;

    Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            try {
            	//获取SelectorImpl的selectedKeys变量和publicSelectedKeys变量(二者类型都是Set<SelectionKey>)
                Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
                
                Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
                if (cause != null) {
                    return cause;
                }
                cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
                if (cause != null) {
                    return cause;
                }
                //将unwrappedSelector的上述两个变量设为selectedKeySet,即采用Netty的
                //SelectedSelectionKeySet代替SelectorImpl的HashSet实现
                selectedKeysField.set(unwrappedSelector, selectedKeySet);
                publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                return null;
            } catch (NoSuchFieldException e) {
                return e;
            } catch (IllegalAccessException e) {
                return e;
            }
        }
    });
    //如果在通过反射赋值变量过程中抛出任何异常
    if (maybeException instanceof Exception) {
        selectedKeys = null;
        Exception e = (Exception) maybeException;
        logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
        //返回SelectorTuple实例,只包含unwrappedSelector
        return new SelectorTuple(unwrappedSelector);
    }
    selectedKeys = selectedKeySet;
    logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
    //返回SelectorTuple实例,包含unwrappedSelector和Netty自带的SelectedSelectionKeySet实例
    return new SelectorTuple(unwrappedSelector,
               new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}

openSelector方法的过程可以概括为:
1、首先通过构造时指定的SelectorProvider的openSelector方法构造一个Selector实现类(Selector是一个抽象类,具体构造的是哪个类的实例可能会因JVM、JDK版本和操作系统类型而有所不同,在Windows下的Oracle JDK1.8中,其实现类为sun.nio.ch.WindowsSelectorImpl)。
2、构造一个SelectedSelectionKeySet对象(Netty自带的类),SelectedSelectionKeySet实现了JDK的AbstractSet类:

final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
	SelectionKey[] keys;
    int size;
    //省略其它方法...
}

SelectedSelectionKeySet类维护了两个实例变量:一个SelectionKey数组和一个int型的变量,表示这个数组拥有的SelectionKey的数量。在构造SelectedSelectionKeySet的时候,数组的初始大小默认为1024,元素的添加从数组的0号位开始逐一添加,当数组满了后,会扩容到原先的2倍。

3、通过当前的系统类加载器加载类sun.nio.ch.SelectorImpl抽象类(AbstractSelector抽象类的子类,也是Selector抽象类的子类,一般HotSpot虚拟机的Selector默认实现类都是sun.nio.ch.SelectorImpl)
如果类加载失败抛出了异常,或者加载的类不是刚才构造的Selector对象所属的类的父类,那么直接构造一个SelectorTuple,并将刚才通过SelectorProvider构造的Selector对象作为参数传入,然后方法返回结束。

SelectorTuple是NioEventLoop的内部类,它提供了两个构造方法并维护了两个实例变量:

private static final class SelectorTuple {
    final Selector unwrappedSelector;
    final Selector selector;

    SelectorTuple(Selector unwrappedSelector) {
        this.unwrappedSelector = unwrappedSelector;
        this.selector = unwrappedSelector;
    }

    SelectorTuple(Selector unwrappedSelector, Selector selector) {
        this.unwrappedSelector = unwrappedSelector;
        this.selector = selector;
    }
}

unwrappedSelector变量表示未经包装的Selector对象(就是通过指定的SelectorProvider构造的Selector实现类),selector变量表示经过io.netty.channel.nio.SelectedSelectionKeySetSelector包装的Selector对象。

4、如果类加载成功并且SelectorProvider.openSelector()返回的对象所属的类是sun.nio.ch.SelectorImpl的子类,那么通过反射获取sun.nio.ch.SelectorImpl的成员变量publicKeys和publicSelectedKeys变量,两者都是Set<SelectionKey>类型:

public abstract class SelectorImpl extends AbstractSelector {
	protected Set<SelectionKey> selectedKeys;
	protected HashSet<SelectionKey> keys;
	private Set<SelectionKey> publicKeys;
	private Set<SelectionKey> publicSelectedKeys;
	//省略其它方法
}

如果在反射获取变量时没有发生异常,那么通过反射将publicKeys和publicSelectedKeys设为刚刚构造的SelectedSelectionKeySet实例,Netty不采用默认HashSet而是自己采用SelectedSelectionKeySet实现publicKeys和publicSelectedKeys实例。在遍历SelectionKey的时候,Netty自带的SelectedSelectionKeySet能够保证更快的迭代速度。

如果在反射获取变量、给变量赋值过程中发生异常,那么会直接构造一个SelectorTuple,并将之前通过SelectorProvider.openSelector方法构造的Selector对象作为参数传入,方法返回结束

5、如果一切正常,将刚才构造的SelectedSelectionKeySet对象赋值给NioEventLoop的selectedKeys变量,然后返回new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
SelectedSelectionKeySetSelector继承了Selector类,持有两个成员变量:

final class SelectedSelectionKeySetSelector extends Selector {
	private final SelectedSelectionKeySet selectionKeys;
    private final Selector delegate;
    //省略其它方法...
}

如果在调用newChild方法过程中抛出任何未捕获的异常,MultithreadEventExecutorGroup会尝试关闭所有已经构造好的NioEventLoop并抛出IllegalStateException异常。

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

完成上述4个步骤后,NioEventLoopGroup就算构造完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值