目录
为什么使用NIO?
概括
那么NIO为什么可以不使用多线程呢?
继续优化
最后总结
首先,NIO指的是非阻塞的IO方式,或者new IO,为什么要使用这种方式?
因为在NIO出现之前,java使用socket来实现网络通信功能,socket的大概流程是:
服务端创建一个ServerSocket, 然后就是客户端用一个Socket去连接服务端的那个ServerSocket, ServerSocket接收到了一个的连接请求就创建一个Socket和一个线程去跟那个Socket进行通讯。
差不多像下面这样:
{ ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池 ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(8088); while(!Thread.currentThread.isInturrupted()) {//主线程死循环等待新连接到来 Socket socket = serverSocket.accept(); executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程 } }这种方式我们叫做BIO,即阻塞IO,也就是比如客户端发送一个请求之后,在服务端响应之前,客户端都会一直阻塞着,因此每一个客户端接入,都需要在服务端创建一个线程来服务这个客户端。因此最可能会导致创建了大量的线程,并且很多线程都是阻塞着的,效率比较低。
尽管也可以使用线程池来管理线程起到一定的缓冲作用,但是当处理百万级的连接时,使用这种模型肯定是不实际的,让cpu去创建这么多的线程是不可能的,因为线程本身是一种非常珍贵的资源,而且这么多线程会占用大量的JVM内存(一个占用1M左右),并且线程上下文切换成本也很高,可能执行线程切换的时间甚至会大于线程执行的时间。因此,BIO模型最本质的问题在于,严重依赖于线程。
那么我们的NIO的作用自然是为了解决BIO的缺点,所以提前给出结论:“NIO可以有效的减少线程数,同时还能实现海量的连接”,一听是不是就很吊。
首先给出一个概念:
系统的IO都可以分为两个阶段:等待就绪阶段和真正的读写IO操作阶段。
需要说明的是等待就绪的阻塞是不使用CPU的,就在那里干等,只有后面的真正的读写IO才是使用cpu的。
NIO和BIO的不同在于:以读read()为例,在BIO的read读里面,它是全程阻塞的(包括等待就绪阶段和真正的读写IO阶段),也就是说只要没有接收到数据,那么会一直阻塞在那里,前面一个等待就绪阶段就是“干等”,连CPU都不需要;后一个IO阶段,是阻塞的,需要使用CPU,但是处理过程很快。也就是说大部分的宝贵时间都浪费在了“干等”上面,甚至连CPU都懒得用!!
而NIO的read在等待就绪阶段都是非阻塞的,也就是说如果没有收到数据,直接返回0 ,执行别的操作,只有真正的读写IO阶段是阻塞的。
因此BIO严重依赖线程的本质是因为进行I/O操作的时候,没法知道当前到底能不能读,能不能写(不像NIO可以利用事件),就算知道了当前不能读写,他也没办法从read和write函数里面返回,因为这两个函数没有办法进行有效中断,所以他们只能从头等到尾,这也就导致一旦请求比较多,BIO只能使用多线程的。
因为NIO的读写函数可以立即返回,这就给了一个不开多线程实现相同效果的机会:这样,如果一个连接(channel)不能读写,那么可以立即切换到别的连接(channel)上进行读写。因此,NIO可以利用“事件模型”来单线程实现处理所有I/O请求,而不必像BIO那样创建很多线程。这里实际上使用的是reactor设计模式:在selector上注册所有感兴趣的事件处理器(handler),单线程轮询选择就绪事件,执行对应的事件处理器(handler)。
因此,NIO可以实现单线程的海量连接,因为节约了线程,连接数大的时候因为线程切换带来的问题也随之解决,并且单线程处理I/O的效率确实非常高,只需要拼命的读、写、选择事件。
但是对于这种“单线程的NIO”用的较少,因为一旦某个hanlder阻塞,会导致整个程序都不能处理新的请求,而且也不能有效利用服务器的多核心资源,因此利用多线程来继续优化这个模型。
那么思考一下至少需要哪些线程?
1、 事件分发器selector(就是一个reactor),单线程轮询选择就绪的事件。
2、具体的I/O操作(hanlder),包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。可以开一个线程池,然后每来一个就往线程池里面提交。
3、具体的业务逻辑,在进行IO之后(也就是读写之后)还要进行进一步的处理,比如对数据库的一些操作。
4、连接的处理和读写的处理通常可以选择分开,这样对于海量连接的注册和读写就可以分发。
因此,NIO对于服务器端的作用就是可以解放线程,处理海量连接。尽管也会使用到多线程,但是相比BIO的多线程,那么线程数少了不知道多少了。
这都是本人对于NIO的作用的理解,具体的使用暂时没说,以后找找机会把。
参考:
https://tech.meituan.com/2016/11/04/nio.html