在上一节中,我们已经做出了一个简单的netty的例子,了解了基本的服务端和客户端的写法,这一章我们来一起学习下netty线程模型的源码分析。
我们在分析源码之前,先考虑一个问题,如何提高NIO的效率?
如何提高NIO的性能?
我们可以举个简单的例子描述下这个问题:
- IO
相当于是在饭店吃饭,有多少个客人就有多少个服务人员
- NIO
相当于是不管饭店来了多少个客人,只有一个服务员在服务,当客人点好餐的时候,叫服务员,服务员再去服务就行了。
那么我们应该如何再一次提高效率呢?我们可以再聘用多个服务生,但是不会再像IO操作一样一个人对应一个服务员(一个server对应着一个客户端,点对点式的),我们可以让一个服务员负责一片的区域,如下图所示:
我们首先要明确几点:
- 一个NIO并不是只有一个selector(一个餐厅不是只有一个服务员)
- 一个selector并不是只能注册一个ServerSocketChannel
这样我们就构成了多线程的NIO。上面的图就是netty工作的线程模型。
这里我们强调一个思想,在多线程中,我们为了避免数据共享的问题,我们可以将一些要处理的数据内容,放到一个阻塞队列中,让需要用到其值的线程自行处理。
源码分析
- 创建一个服务类
// 1.先创建一个服务类
ServerBootstrap bootstrap = new ServerBootstrap();
- 创建两个线程池,分别是boss和worker
(1)boss用来监控端口等信息,就是用来监控客户端的连接
(2)worker用来监控数据的接受和发送
// 2.创建两个线程池,一个负责监控客户端的连接,一个负责监控客户端发送的数据
ExecutorService boss = Executors.newCachedThreadPool();
ExecutorService worker = Executors.newCachedThreadPool();
- 设置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个参数的构造函数
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
这里的代码分析可能不是很准确,希望大家指正错误。