netty(2)--netty线程模型的源码分析

在上一节中,我们已经做出了一个简单的netty的例子,了解了基本的服务端和客户端的写法,这一章我们来一起学习下netty线程模型的源码分析。
我们在分析源码之前,先考虑一个问题,如何提高NIO的效率?

如何提高NIO的性能?

我们可以举个简单的例子描述下这个问题:

  • IO
    相当于是在饭店吃饭,有多少个客人就有多少个服务人员
    在这里插入图片描述
  • NIO
    相当于是不管饭店来了多少个客人,只有一个服务员在服务,当客人点好餐的时候,叫服务员,服务员再去服务就行了。
    在这里插入图片描述
    那么我们应该如何再一次提高效率呢?我们可以再聘用多个服务生,但是不会再像IO操作一样一个人对应一个服务员(一个server对应着一个客户端,点对点式的),我们可以让一个服务员负责一片的区域,如下图所示:
    在这里插入图片描述
我们首先要明确几点:
  1. 一个NIO并不是只有一个selector(一个餐厅不是只有一个服务员)
  2. 一个selector并不是只能注册一个ServerSocketChannel
这样我们就构成了多线程的NIO。上面的图就是netty工作的线程模型。

这里我们强调一个思想,在多线程中,我们为了避免数据共享的问题,我们可以将一些要处理的数据内容,放到一个阻塞队列中,让需要用到其值的线程自行处理。

源码分析

  1. 创建一个服务类
// 1.先创建一个服务类
ServerBootstrap bootstrap = new ServerBootstrap();
  1. 创建两个线程池,分别是boss和worker
    (1)boss用来监控端口等信息,就是用来监控客户端的连接
    (2)worker用来监控数据的接受和发送
// 2.创建两个线程池,一个负责监控客户端的连接,一个负责监控客户端发送的数据
ExecutorService boss = Executors.newCachedThreadPool();
ExecutorService worker = Executors.newCachedThreadPool();
  1. 设置NIO SOCKET工厂,传入两个线程池
        // 给服务类设置一个工厂
        // 将两个线程池传入
        // 设置NIO socket工厂
        bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));

这里我们需要看下这个步骤的源码

3.1
new NioServerSocketChannelFactory(boss, worker)
创建工厂对象,传入boss和worker线程池

    public NioServerSocketChannelFactory(Executor bossExecutor, Executor workerExecutor) {
        this(bossExecutor, workerExecutor, getMaxThreads(workerExecutor));
    }

如上所示其实是增加了参数getMaxThreads(workerExecutor),并且调用重载的有参构造器。
getMaxThreads(workerExecutor)的代码是:

    private static int getMaxThreads(Executor executor) {
        if (executor instanceof ThreadPoolExecutor) {
            int maxThreads = ((ThreadPoolExecutor)executor).getMaximumPoolSize();
            return Math.min(maxThreads, SelectorUtil.DEFAULT_IO_THREADS);
        } else {
            return SelectorUtil.DEFAULT_IO_THREADS;
        }
    }

其中getMaximumPoolSize是得到我们传入的线程池的数量;
SelectorUtil.DEFAULT_IO_THREADS则是代表了常量:

static final int DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors() * 2;

表示的是当前CPU活跃的线程数量*2
如果我们传入的是一个线程池,最后取二者的最小值进行返回
如果我们传入的不是一个线程池,那么我们返回的是 SelectorUtil.DEFAULT_IO_THREADS, (就是当前活跃的CPU的线程数 *2)

3.2
继续调用构造器

public NioServerSocketChannelFactory(Executor bossExecutor, Executor workerExecutor, int workerCount) {
        this(bossExecutor, 1, workerExecutor, workerCount);
    }

增加了参数传向下一个重载的构造函数

3.3
继续调用4个参数的构造函数

扫描二维码关注公众号,回复: 5936755 查看本文章
    public NioServerSocketChannelFactory(Executor bossExecutor, int bossCount, Executor workerExecutor, int workerCount) {
        this(bossExecutor, bossCount, new NioWorkerPool(workerExecutor, workerCount));
    }

参数的含义是boss线程,boss线程池的数量,worker线程池,woker线程池的数量,这里面我们将boss线程池的数量规定为1,worker线程池的数量可以参考上面的方法getMaxThreads,由该方法决定。

观察代码new NioWorkerPool(workerExecutor, workerCount)

     public NioWorkerPool(Executor workerExecutor, int workerCount) {
        this(workerExecutor, workerCount, (ThreadNameDeterminer)null);
    }

    public NioWorkerPool(Executor workerExecutor, int workerCount, ThreadNameDeterminer determiner) {
        super(workerExecutor, workerCount, false);
        this.determiner = determiner;
        this.init();
    }

同样也是构造函数的调用,由两个参数变为三个参数的构造。其中第三个参数是null;

我们先看下该方法的super(workerExecutor, workerCount, false);

	/**
	 * 传入的三个参数分别是worker线程池,负责监听信息的交互,worker线程的数量,是否是自动初始化,上面调用的时候传递的是false
	 */
    AbstractNioWorkerPool(Executor workerExecutor, int workerCount, boolean autoInit) {
    	// 新建一个原子的整型对象
        this.workerIndex = new AtomicInteger();
        this.initialized = new AtomicBoolean(false);
        if (workerExecutor == null) {
            throw new NullPointerException("workerExecutor");
        } else if (workerCount <= 0) {
            throw new IllegalArgumentException("workerCount (" + workerCount + ") " + "must be a positive integer.");
        } else {
        	// 创建一个AbstractNioWorker对象数组,长度是workerCount
            this.workers = new AbstractNioWorker[workerCount];
            this.workerExecutor = workerExecutor;
            // 如果是true调用,刚开始初始化的时候,该值是false
            if (autoInit) {
                this.init();
            }

        }
    }

我们将代码的部分解释写到了注释中,可以参考其注释。
我们再来看下NioWorkerPool方法中的this.init()方法(该方法和AbstractNioWorkerPool构造方法的init是一个方法)

    protected void init() {
        if (!this.initialized.compareAndSet(false, true)) {
            throw new IllegalStateException("initialized already");
        } else {
            for(int i = 0; i < this.workers.length; ++i) {
            	// 根据worker的数量,对workers进行初始化
                this.workers[i] = this.newWorker(this.workerExecutor);
            }

            this.waitForWorkerThreads();
        }
    }

从this.newWorker(this.workerExecutor)向上追述代码:

    AbstractNioSelector(Executor executor, ThreadNameDeterminer determiner) {
        this.id = nextId.incrementAndGet();
        this.startupLatch = new CountDownLatch(1);
        this.wakenUp = new AtomicBoolean();
        this.taskQueue = new ConcurrentLinkedQueue();
        this.shutdownLatch = new CountDownLatch(1);
        this.executor = executor;
        // 用来开启selector
        this.openSelector(determiner);
    }

进入openSelector()

private void openSelector(ThreadNameDeterminer determiner) {
        try {
        	// 开启一个selector(worker线程池的)
            this.selector = SelectorUtil.open();
        } catch (Throwable var11) {
            throw new ChannelException("Failed to create a selector.", var11);
        }

        boolean success = false;

        try {
        	// 启动线程,将worker线程启动,自动调用其run方法
            DeadLockProofWorker.start(this.executor, this.newThreadRenamingRunnable(this.id, determiner));
            success = true;
        } finally {
            if (!success) {
                try {
                    this.selector.close();
                } catch (Throwable var10) {
                    logger.warn("Failed to close a selector.", var10);
                }

                this.selector = null;
            }

        }

        assert this.selector != null && this.selector.isOpen();
    }

启动了线程之后,自动调用worker线程池的run方法

public void run() {
        this.thread = Thread.currentThread();
        this.startupLatch.countDown();
        int selectReturnsImmediately = 0;
        Selector selector = this.selector;
        if (selector != null) {
            long minSelectTimeout = SelectorUtil.SELECT_TIMEOUT_NANOS * 80L / 100L;
            boolean wakenupFromLoop = false;

            while(true) {
                this.wakenUp.set(false);

                try {
                    long beforeSelect = System.nanoTime();
                    int selected = this.select(selector);
                    if (selected == 0 && !wakenupFromLoop && !this.wakenUp.get()) {
                        long timeBlocked = System.nanoTime() - beforeSelect;
                        if (timeBlocked < minSelectTimeout) {
                            boolean notConnected = false;
                            Iterator i$ = selector.keys().iterator();

                            while(i$.hasNext()) {
                                SelectionKey key = (SelectionKey)i$.next();
                                SelectableChannel ch = key.channel();

                                try {
                                    if (ch instanceof DatagramChannel && !ch.isOpen() || ch instanceof SocketChannel && !((SocketChannel)ch).isConnected() && !((SocketChannel)ch).isConnectionPending()) {
                                        notConnected = true;
                                        key.cancel();
                                    }
                                } catch (CancelledKeyException var18) {
                                }
                            }

                            if (notConnected) {
                                selectReturnsImmediately = 0;
                            } else if (Thread.interrupted() && !this.shutdown) {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Selector.select() returned prematurely because the I/O thread has been interrupted. Use shutdown() to shut the NioSelector down.");
                                }

                                selectReturnsImmediately = 0;
                            } else {
                                ++selectReturnsImmediately;
                            }
                        } else {
                            selectReturnsImmediately = 0;
                        }
                    } else {
                        selectReturnsImmediately = 0;
                    }

                    if (SelectorUtil.EPOLL_BUG_WORKAROUND) {
                        if (selectReturnsImmediately == 1024) {
                            this.rebuildSelector();
                            selector = this.selector;
                            selectReturnsImmediately = 0;
                            wakenupFromLoop = false;
                            continue;
                        }
                    } else {
                        selectReturnsImmediately = 0;
                    }

                    if (this.wakenUp.get()) {
                        wakenupFromLoop = true;
                        selector.wakeup();
                    } else {
                        wakenupFromLoop = false;
                    }

                    this.cancelledKeys = 0;
                    this.processTaskQueue();
                    selector = this.selector;
                    if (this.shutdown) {
                        this.selector = null;
                        this.processTaskQueue();
                        Iterator i$ = selector.keys().iterator();

                        while(i$.hasNext()) {
                            SelectionKey k = (SelectionKey)i$.next();
                            this.close(k);
                        }

                        try {
                            selector.close();
                        } catch (IOException var16) {
                            logger.warn("Failed to close a selector.", var16);
                        }

                        this.shutdownLatch.countDown();
                        return;
                    }

                    this.process(selector);
                } catch (Throwable var19) {
                    logger.warn("Unexpected exception in the selector loop.", var19);

                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException var17) {
                    }
                }
            }
        }
    }

上面的run方法,主要是做了:
循环执行:(worker和boss是一样的执行过程)
(1)标记一个wakeup状态

wakenUp.set(false)

(2)select操作

int selected = this.select(selector);

(3)执行任务队列的任务处理

this.processTaskQueue();

(4)执行业务处理

this.process(selector);

这里的第四部process也是一个抽象,他有自己的实现,他的实现有worker和boss的实现,boss负责监听端口,连接,worker负责数据的读取传输,这里代码就不给出了。底层基本上就是NIO的服务端和客户端的实现思路

注意一点
boss的select是阻塞的,不同于worker的select是等待500ms,没有任务的时候boss的select是对其阻塞。boss的任务指的是监听端口的任务:OP_ACCEPT

这里的代码分析可能不是很准确,希望大家指正错误。

猜你喜欢

转载自blog.csdn.net/u014437791/article/details/89147883