同步与异步
同步是发起一个调用后,被调用者未处理完请求前,调用不返回,调用者不继续执行
异步是发起一个调用后,可以马上收到被调用者已接到请求的回应,但并没有立即返回结果。调用者可以继续执行其它操作,被调用者通过事件、回调等机制通知调用者返回结果
阻塞与非阻塞
操作系统I/O分为两个阶段:等待就绪和操作。读函数分为等待系统可读和真正的读;写函数分为等待网卡可写和真正的写。等待就绪的阻塞不使用 CPU,是在“空等”;真正的读写操作的阻塞是在使用 CPU,真正在“干活”,这个memory copy 的过程很快(如宽带 1GB/s)。
传统的BIO里面socket.read(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。
最新的AIO(Async I/O)里面会更进一步:不但等待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。
换句话说,BIO里用户最关心“我要读”,NIO里用户最关心”我可以读了”,在AIO模型里用户更需要关注的是“读完了”。
NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。
BIO(Blocking I/O)
同步阻塞IO,数据的读入和写出阻塞在一个线程内。服务端一般通过在while(true)循环中调用accept()方法等待接受客户端连接请求,一旦接收到一个连接请求,就可以建立通信套接字并在套接字上进行读写操作,单线程情况下不能再接收其它请求,只能等当前连接的客户端操作执行完,不过可以通过多线程来支持多个客户端的连接。当连接数达到十万甚至百万是,BIO模型无能为力,需要更加高效的IO模型。
NIO(Non-BlockingIO)
面向缓冲的,基于通道IO的同步非阻塞IO模型,提供了SocketChannel和ServerSocketChannel两种套接字分别与BIO模型的Socket和ServerSocket对应。这两种套接字都支持阻塞和非阻塞两种模式。对于低负载、低并发的应用,可以使用同步阻塞IO来提升开发效率并提高可维护性;对于高负载、高并发的应用,应采用NIO非阻塞模式开发。
BIO是面向流(Stream oriented),而NIO是面向缓冲区的(Buffer oriented)。Stream中以Buffer开头的扩展类,只是流的包装类,要从流读到缓冲区,而NIO是直接读到缓冲区进行读写操作。最常用的缓冲区是ByteBuffer,它提供了一组用于操作byte数组的功能。每一种Java基本类型(除了Boolean)都对应一种缓冲区。
NIO包含Channel(通道)、Buffer(缓冲区)和Selector(选择器)这三个核心组件,通道只和Buffer交互,因为Buffer,通道可以异步进行读写。选择器用于使用单个线程处理多个通道,减少了线程之间切换带来的开销,可以提高系统效率。
NIO 主要事件有:读就绪、写就绪、有新连接到来
NIO 有原来的阻塞读写变成单线程轮询写事件,除了事件的轮询是阻塞的(没有可干的事情必须阻塞),其它 I/O 操作都是纯 CPU 操作,没有必要开启多线程。并且由于多线程处理阻塞 IO (BIO)导致的线程切换问题也得到了解决,为处理海量连接提供了可能。
select是阻塞的,无论是通过操作系统的通知(epoll)还是不停的轮询(select,poll),这个函数是阻塞的。所以你可以放心大胆地在一个while(true)里面调用这个函数而不用担心CPU空转
AIO(Asynchronous IO)
即NIO 2,java 7开始引进,是NIO的改进版,是异步非阻塞IO模型。异步IO基于事件和回调机制实现,应用执行IO操作后直接返回,不会阻塞。当后台处理完成后,操作系统会通知相应线程进行后续操作。