一、传统IO线程模型的劣势
在NIO出现之前,我们使用的其实都是传统的IO模型
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8088);
new Thread(() -> {
try {
Socket accept = serverSocket.accept();
new Thread(() -> {
try {
int len;
byte[] data = new byte[1024];
InputStream inputStream = accept.getInputStream();
while ((len = inputStream.read(data)) != -1) {
System.out.println(new String(data, 0, len));
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();
}
上述代码处理连接时,只能由一个线程处理一个连接,意味着如果有1000个客户端连接就得开1000个线程。且在每次线程使用完之后都需要销毁,这无疑是影响性能的,且频繁的线程上下文切换也是不合理的。而在传统IO的处理中我们无非是使用线程池替换传统线程的创建和销毁,但是这也没有从根本上改变这一问题
二、Reactor模型
Reactor模型是一种常用的高性能IO模型,一种异步、非阻塞的事件驱动的模型,有三种实现方式
2.1 基于单线程实现
上面图中描述的就是单线程实现Reactor,由一个线程来接收客户端连接,通过dispatcher将对应的请求数据分发到指定的handler进行编码处理,对于并发小的场景可以适用。但是在高并发情况下这种模型还是无法满足的,其原因在于:
- 单线程NIO无法支撑过多的连接接入
- CPU负载过高,处理就会变慢,就会造成大量客户端连接超时
- 低可靠性,如果出现死循环那么整个功能将不可用
- 无法利用多核CPU的优势,造成资源浪费
2.2 Reactor多线程实现
多线程实现实际上是优化了原先只有一个线程处理事件带来的弊端,可以基于java自身的线程池实现,其变化在于,连接进来时,会将其分配到对应的线程,由对应的线程进行处理,在处理连接时使用的依旧时单线程
2.3 主从多线程
优化连接处理部分,使用多线程,当一个客户端连接进来时,主线程池会选出一个NIO线程,充当Acceptor,负责处理新接入的连接。连接创建完成之后,使用Dispatcher派发到主线程池,主线程池将新连接绑定到从线程池的一个NIO线程并处理后续读写操作
三、Netty对三种线程模型的支持
- 单线程模式
public ServerBootstrap group(EventLoopGroup group) {
return group(group, group);
}
- 多线程和主从多线程模式
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup;
return this;
}
在构建ServerBootstrap实例时,传入单个NioEventLoopGroup对象构建的是单线程的,传入worker和boss两个NioEventLoopGroup构建的是多线程的,指定boss这个NioEventLoopGroup的线程数或者不传(依据当前计算机CPU核数决定构建的线程池大小)则构建的是主从多线程模式