一、Java NIO
1.1阻塞IO概述
通常在进行同步I/О操作时,如果读取数据,代码会阻塞直至有可供读取的数据。同样,写入调用将会阻塞直至数据能够写入。传统的Server/Client模式会基于TPR(Thread per Request),服务器会为每个客戌端请求建立一个线程,由该线程单独负责处理一个客户请求。这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池线程的最大数量,这由带来了新的问题,如果线程池中有100个线程,而有100个用户都在进行大文件下载,会导致第101个用户的请求无法及时处理,即便第101个用户只想请求一个几KB大小的页面。
1.2 NIO(非阻塞IO)概述
- 当有读或写的注册事件发生,可以从Selector中获取相应的SelectionKey,同时可以从SelectionKey中找到发生的时间和发生的具体的SelectableChannel。
- 非阻塞指的是IO事件本身不阻塞,但是获取IO事件的select()方法是需要阻塞等待的.区别是阻塞的IO会阻塞在IO操作上, NIO阻塞在事件获取上,没有事件就没有IO,从高层次看IO就不阻塞了.也就是说只有IO已经发生那么我们才评估IO是否阻塞,但是select()阻塞的时候IO还没有发生,何谈IO的阻塞呢?NIO的本质是延迟IO操作到真正发生IO的时候,而不是以前的只要IO流打开了就一直等待IO操作。
1.2.1JAVA NIO 组成
Channel
- Channel是一个通道,可以通过它读取和写入数据,它就像水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(一个流必须是 InputStream 或者OutputStream的子类),而且通道可以用于读、写或者同时用于读写。因为Channel是全双工的,所以它可以比流更好地映射底层操作系统的APl。
- NIO中通过channel封装了对数据源的操作,通过channel我们可以操作数据源,但又不必关心数据源的具体物理结构。这个数据源可能是多种的。比如,可以是文件,也可以是网络socket。在大多数应用中,channel与文件描述符或者socket是——对应的。Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。
分类
- FileChannel从文件中读写数据。
- DatagramChannel能通过UDP读写网络中的数据。
- SocketChannel 能通过TCP读写网络中的数据。’
- ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
1.2.2 Scatter/Gather
分散(Scatter)
- 从Channel中读取数据,数据分散到多个buffer中去。
聚集(Gather)
- 将多个buffer中的数据聚集到一个channel中去。
2.1 Buffer
缓冲区。本质上为可以读取数据,也可以写入数据的一块内存。
2.1.1 使用Buffer步骤
- 写数据到Buffer
- 调用filp()方法
- 从Buffer读取数据
- 调用clear()方法或compact()方法 (clear会清空缓冲区,compact会清空已经读过的数据)
2.1.2 Buffer的三个属性
(1) Capacity
- 内存块的大小,一旦Buffer满了,需要清空。
(2) Position
- 写模式中,position表示当前写入的位置,position最大为capacity-1。读模式下,为读入数据的当前位置。
(3) limit
- 写数据时,能写入多少的数据,写模式下,limit等于capacity。读数据时,表示有多少数据可读,在写模式下就是position。
2.2 缓冲区分片(根据现有缓冲区分片多个子缓冲区)
- 只读缓冲区:只能读数据,不能写入。
- 直接缓冲区:为了加快I/O速度。
- 内存映射文件I/O。
3.1 Selector
- 可以使用单线程管理多个channel,也就是管理多个网络连接,selector相当于多路复用器,使用更少的线程,相比使用多个线程,避免上下文切换。
- 一些channel是不可被复用,例如filechannel,只有继承了SelectorableChannel才能被复用。
3.1.2 channel注册到Selector
- 注册
Channel.resiter(Selector sel,int ops)
sel为选择器,ops为需要查询的通道的操作(可读,可写,连接,接收)- 选择键
(1) Channel注册到后,并且一旦通道处于某种就绪的状态,就可以被选择器查询到。这个工作,使用选择器Selector的select ()方法完成。select方法的作用,对感兴趣的通道操作,进行就绪状态的查询。
(2) Selector可以不断的查询Channel中发生的操作的就绪状态。并且挑选感兴趣的操作就绪状态。一旦通道有操作的就绪状态达成,并且是Selector感兴趣的操作,就会被Selector选中,放入选择键集合中。
(3)一个选择键,首先是包含了注册在Selector的通道操作的类型,比方说SelectionKey.OP.READ。也包含了特定的通道与特定的选择器之间的注册关系。开发应用程序是,选择键是编程的关键。NIO的编程,就是根据对应的选择键,进行不同的业务逻辑处理。
3.2 NIO管道(pipe)
Java NIO管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
3.3 文件锁(FileLock)
- 文件锁是进程级别的,不是线程级别的,不能解决多线程同时访问一个文件的,修改同一文件的问题。同一个进程,多个线程能够同时访问。
- 文件锁是程序所属JVM所持有,要调用release()或者关闭对应的FileChannel对象或者程序退出,才会释放对应的文件锁。