nio与bio
阻塞io(bio)使用线程池问题
1.线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。
2.线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。
3.线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。
4.容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。
常见I/O模型对比
所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。
需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在"干活",而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。
nio指non-blocking,io非阻塞io
aio指async io,异步io
传统的BIO里面socket.read(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。
最新的AIO(Async I/O)里面会更进一步:不但等待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。
BIO与NIO对比
block IO与Non-block IO
1)区别
IO模型 IO NIO
方式 从硬盘到内存 从内存到硬盘
通信 面向流(乡村公路) 面向缓存(高速公路,多路复用技术)
处理 阻塞IO(多线程) 非阻塞IO(反应堆Reactor)
触发 无 选择器(轮询机制)
2)面向流与面向缓冲
Java NIO和IO之间第一个最大的区别是,IO是面向流的.NIO是面向缓冲区的。Java IO面向流意味着毎次从流中读一个成多个字节,直至读取所有字节,它们没有被缓存在任何地方,此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的教据,需要先将它缓存到一个缓冲区。Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,霱要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数裾。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
3)阻塞与非阻塞
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
4)选择器(Selector)
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择"通道:这些通里已经有可以处理的褕入,或者选择已准备写入的通道。这选怿机制,使得一个单独的线程很容易来管理多个通道。
5)NIO和BIO读取文件
BIO读取文件:链接
BIO从一个阻塞的流中一行一行的读取数据
image | left | 469x426
NIO读取文件:链接
通道是数据的载体,buffer是存储数据的地方,线程每次从buffer检查数据通知给通道
image | left | 559x394
6)处理数据的线程数
NIO:一个线程管理多个连接
BIO:一个线程管理一个连接
NIO工作原理
主要几个部分是Channel(通道),Buffer(缓冲区),Selector(选择器)。
Channel提供从文件、网络读取数据的通道,但是读取或写入的数据都必须经由Buffer。通道是双向的,通过一个Channel既可以进行读,也可以进行写
Buffer,故名思意,缓冲区,实际上是一个容器,是一个连续数组。服务端接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。
Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。
其中,一个线程管理一个Selector,而一个Selector管理多个Channel,被管理的Channel需要在该Selector上注册自己感兴趣的事件,如Accept,Read,Write等。
每个Channel对应一个缓冲区Buffer,每次Channel中有数据可以读写的时候,就读写到缓冲区中。然后程序再对缓冲区进行操作。
这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用相应的方法或者线程来进行读写、操作,大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多个空闲连接的多线程之间的上下文切换导致的开销。