在java种我们所说的IO与NIO其实是java对操作系统的IO模型的封装而产生的接口,是我们开发人员不必关注操作系统层面的知识。
一请求一应答,客户端发起socket连接,服务端通过serversocket进行连接。但是对于多个连接请求这样的模型就没法处理了这样衍生出来的就是多线程了。
多个连接请求发送到服务端后,服务端每收到一个请求就开一个线程去为这个请求建立连接,这样就可以完成多连接请求的需求了。
**问题一:**为什么采用多线程,而不一个一个处理连接请求呢?
答:由于socket的accept、read、write方法都是阻塞的,如果一旦一个连接需要很长时间的读写,那么后续的连接就无法及时的响应了。
**问题二:**多线程的方式有什么问题,该如何解决?
答:采用多线程去解决这种场景的问题一旦请求量过大的时候就会无休止的创建线程,但线程资源很宝贵,在linux种进程的本质就是线程,所以一旦创建一个线程调用的是系统及的函数,开销很大。一旦线程的创建以及切换时间大于线程实际的执行时间就得不偿失了。而且线程的创建过多会影响jvm的可用内存的。
解决方法:可以采用线程池的方法去做,规定线程数量,防止线程创建过多,同时线程复用解决了线程频繁创建与销毁的开销。传统的BIO模型是一种同步的IO模型,采用线程池是一种伪异步的IO模型(看起来连接都扔给线程池去处理了然后客户端可以立刻得到回应,似乎是异步的但线程池中还是同步操作)。
**总结:**对于少量的连接来说,BIO无疑可以提供稳定的服务,但是对于大量的服务来说这样的模型就不够用了。
NIO是一种同步非阻塞的I/O模型,jdk对应 java.nio 包下,提供了 Channel , Selector,Buffer。通过这三个抽象类的实现类程序员就可以自己去完成NIO编程了。
**一、**NIO流是不阻塞的,IO流是阻塞的。
NIO为我们提供了与普通的IO(socket和serversocket)对应的两个socket类-------------socketChannel和serverSocketChannel。
通过这两个类就可以在连接时配置是否异步。如果异步就是从channel读取数据到buffer中时可以同时去做其他的事情。在NIO中数据只能时channel和buffer的交互。
二、buffer区
buffer是一个对象,里边可以存一些要读出或者写入的对象,他有左右基础数据类型相应的实现类除了boolean。bytebuffer又有两种实现:(这个bytebuffer的两种方法还可以细说之后的文章再分享)
**position:**像指针一样表示所在的位置方便读写。当为0就意味着从头开始。
**limit:**就是当前实际可以存储的最大容量。
capacity:就是缓存的最大值,此属性的特点就是不可能为负值,buffer一旦创建,就会不改变
而IO是面向流的,它不能像buffer一样对数据进行随意的操作,它只能按顺序的去读取去修改,如果想要为流中的数据交换位置就要给他缓存一份数据再去做。
三、channel
NIO通过channel进行读写,只与buffer交互它是双向的可读可写,可以异步的读写,但是流的读写是单向的要么读要么写,它的读写都有自己的实现类。
四、Selector
NIO有选择器,IO没有。普通的IO来一个连接就要接收去处理,但是Selector可以将所有的连接先注册再选择器上,然后再由一个线程去控制选择器去操作对应的channel。
jdk没有提供写好的NIO框架,需要用户通过这些提供的工具去自己搭建NIO框架。很麻烦。。。
所有的IO都有两个阶段,就绪阶段和操作阶段,就绪阶段就是有数据传递过来了可以进行读写了,这一段时间是不占用cpu的,正真的读写才占用
可以采用事件驱动模型去做:
1.为读、写、连接这几个事件注册好相应的方法。
2.建立两个selector,分别由两个线程管理。一个负责注册所有的channel,并把channel同时注册到另一个selector上,另一个selector通过select()阻塞方法选取就绪的channel进行真正的读写操作。这样CPU就可以专注的去读写让cpu最高效了。
3.每当有一个channel可执行就通过得到它相应的事件去触发之前注册的事件对应的handle即可。