要理解NIO必须先理解Tomcat的BIO,传统BIO模式主要这样的一个运行模式。当一个HTTP请求过来时,服务器立马调用serverSocket.accept()接受一个socket,然后服务器从线程池中取一个线程并且将这个socket传给这个线程处理。这个线程就会调用读数据流的方法读取socket中携带的数据流,然后将读取的数据流利用http协议解析成httprequest对象,然后将这个对象传给servlet处理。当servlet处理完毕之后该线程将处理后返回的数据再调用写方法写到客户端。但是这个过程存在一个问题,就是一般的io操作都比较慢。比如线程在读数据的时候,因为io操作比较慢所以要线程要等待io准备好读的数据后才能读取数据,在io准备数据的时候线程一直处于等待浪费状态。在比如当线程往外写数据的时候,因为io处理比较慢所以线程要等待io把线程写入到数据都输出完毕并且关闭socket后才释放这个线程,在io往外输出的这段时间内线程也处于等待浪费的状态。所以这就是IO操作对线程的阻塞作用。从上面的过程可以看出因为socket和线程是绑定的,所以有多少个线程那么就只能维持多少个socket连接。这就大大的影响了Tomcat的并发量。
要增加并发量就必须要解决io操作对线程的阻塞作用。这样NIO模式就应用而生了。所谓NIO模式就是在IO操作(读或者写)的阻塞线程的时候要把线程释放掉,让线程去处理其他的没有阻塞的socket的io流去。这样就提高了线程的利用率,相同的线程可以处理的socket连接要比BIO模式更多了。NIO模式创建了Channel和Selector两个概念。当一个http请求过来的时候,服务器立马将接受到的socket扔给一个Channel,由这个Channel保持和客户端的连接状态。这个Channel会去selector注册一个连接事件,但是这时候还不会调用线程让线程操作io流。当Channel将所有的数据都写入到自己的buffer中后会向selector注册一个读事件。服务器的一个线程遍历selector的时候发现有一个读事件才会调用一个线程池中的线程,并且将这个通道传给这个线程让线程去读取缓冲区中已经准备好的数据并且组织解析好数据调用servlet处理数据。等这个线程将数据返回的时候,会将返回的数据写入到这个Channel的写buffer中去,然后向selector注册一个写事件。这时候不用等待io都把数据输出后才释放线程就可以直接释放线程了。服务器的一个线程循环selector发现有一个写事件,这时候将buffer中的数据输出到客户端。这样使用同样的线程数相比BIO模式可以大大的增加socket连接数量,并且增加了线程的数据处理效率,最终增加了Tomcat可以承载的并发量。