一、传统IO(BIO)和NIO
BIO是同步阻塞的,即一个请求分配一个线程,就是再客户端发送请求到服务器的时候,服务器会为这个客户端分配一个线程去处理相关的读写操作,且在此期间,该线程是阻塞的,不能接收其他客户端的请求。这样的阻塞如果是单线程的话,在多用户的情况下会造成非常不好的系统体验,且会造成cpu的浪费,所以需要用多线程来处理这样的请求。但是使用多线程也有缺点:线程创建和销毁的成本较高;线程还会占有一定的内存,如果线程的数量较多,数千的时候,会造成吃掉一半的jvm内存;另外线程切换的成本也很高,因为线程切换需要保存线程的上下文信息,方便下次线程被唤醒后继续处理。
NIO是面对缓冲区的,是同步非阻塞的,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
二、NIO的三个核心组件介绍
1、selector
selector是NIO的选择器,NIO的同步功能就是靠selector来实现的,多个channel可以注册到同一个selector上,然后用一个线程来管理一个selector选择器,就实现了通过一个线程来管理多个通道实现客户端同步请求的效果。
我们可以将Server端的监听连接请求的事件和处理请求的事件放在一个线程中,但是在事件应用中,我们通常会把它们放在两个线程中:一个线程专门负责监听客户端的连接请求(OP_ACCEPT),而且是
以阻塞方式执行的
;另外一个线程专门负责处理请求(OP_READ、OP_WRITE等),这个专门处理请求的线程才会真正采用NIO的方式
。
2、channel
NIO通道是一个
对象
,可以通过它
(从客户端写入缓冲区)
读取和写入数据
(从缓冲区通过通道发送给客户端)
,可以理解为是对原I/O包中流的模拟,通道和流的区别在于,通道是双向的,通道可用于
读、写或者同时读写
,而流只能是一个方向,inputstream是读,outputstream是写。
3、buffer
NIO缓冲区就是一个数组,有byteBuffer、charBuffer、floatBuffer、LongBuffer等。
数据总是从通道读取到缓冲区,或者从缓冲区写入到通道
。
三、先看下服务器端和客户端的代码
1、服务器端
public void selector() throws IOException {
//先给缓冲区申请内存空间
ByteBuffer buffer = ByteBuffer.allocate(1024);
//打开Selector为了它可以轮询每个 Channel 的状态
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(fal