本系列的上一篇文章已经介绍过,IO的阻塞与非阻塞看的是发起IO请求时是否会被阻塞。
一、使用阻塞IO:
在原来进行Java Socket网络编程时,每打开一个I/O通道,read()就一直等待读取字节内容,如果内容没有准备好,read()会阻塞直到数据到来,但此时线程不能做其它事情,所以解决方法就是开辟一个线程池,把每个请求分发到一个线程中去,让线程去等待。
存在的问题:
一个客户端一个线程的方式去处理,则由于创建、维护和切换线程需要的系统开销导致系统扩展性方面受到了很大限制。对于连接生存期比较长的协议来说,线程池的大小仍然限制了系统可以同时处理的客户端数量。如果增加线程池的大小,将带来更多的线程处理开销,而不能提升系统的性能,因为在大部分的时间里客户端是处于空闲状态的。
二、对阻塞IO的改进:
根据上文中的情况,由于要一直等待数据准备好,所以进程会一直阻塞直到有数据进来。因此能不能在有数据进来时自动通知,这样就不必开启多个线程死等,从而也就不堵塞了。
其实这就是NIO中就使用的一种多线程模式reactor(有些文章翻译成反应器或反应堆模式)。
三、关于Reactor模式:
Reactor:负责响应IO事件,当检测到一个新的事件,将其发送给相应的Handler去处理。
Reactor在Java NIO中由JDK类库提供的Selector类实现,循环监听所有注册到Selector上Channel的事件,在没有感兴趣的事件时阻塞。
事件类型及对应常量值:
读事件:SelectionKey.OP_READ(1)
写事件:SelectionKey.OP_WRITE(4)
客户端连接服务端事件:SelectionKey.OP_CONNECT(8)
服务端接收客户端连接事件:SelectionKey.OP_ACCEPT(16)
Acceptor:负责接受客户端连接,并实现分派任务操作处理。
Handler:负责处理请求(read...send),同时将handler与事件绑定。
关于Reactor模式,这里有一个关于服务员处理顾客点餐的比喻,我觉得很生动形象,看过后应该更容易理解,分享给大家:http://daimojingdeyu.iteye.com/blog/828696
四、Java NIO:
Java BIO中,一直使用流的方式完成I/O。所有I/O都被视为单个的字节移动,通过Stream对象一次移动一个字节。
Java NIO使用不同的方式--块I/O,块I/O的效率可以比流I/O高许多。
面向流的I/O一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。面向流的I/O通常相当慢。
面向块的I/O以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按流式的字节处理数据要快得多。但是面向块的I/O缺少一些面向流的 I/O 所具有的优雅性和简单性。
1、缓冲区:
Java NIO 中,所有数据都是用缓冲区处理的。
在读取数据时,从通道中读取的任何数据是直接读到缓冲区中的。
在写入数据时,发送给通道的所有对象都必须首先放到缓冲区中。
缓冲区实质上是一个数组,但缓冲区不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程
(1)缓冲区分配和包装:
在能读写之前,必须有一个缓冲区。要创建缓冲区,您必须分配它。我们使用静态方法allocate()来分配缓冲区,值得注意的是 Buffer 及其子类都不是线程安全的。
ByteBuffer buffer = ByteBuffer.allocate(1024);
将一个现有的数组转换为缓冲区:
byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);
slice()方法根据现有的缓冲区创建子缓冲区
buffer.position(3);
buffer.limit(7);
ByteBuffer slice = buffer.slice();
新缓冲区与原来的缓冲区共享一部分数据,如果修改子缓冲区中的数据,原缓冲区内对应数据也会被修改。
(2)只读缓冲区
可以读取它们,但是不能向它们写入。通过调用缓冲区的asReadOnlyBuffer()方法,将任何常规缓冲区转换为只读缓冲区。
(3)直接缓冲区
为加快I/O速度,而以一种特殊的方式分配其内存的缓冲区。
Sun文档:
给定一个直接字节缓冲区,Java虚拟机将尽最大努力直接对它执行本机I/O操作。
也就是说,它会在每一次调用底层操作系统的本机I/O操作之前(或之后),尝试避免将缓冲区的内容拷贝到一个中间缓冲区中(或者从一个中间缓冲区中拷贝数据)。
(4)内存映射文件I/O:
只有文件中实际读取或者写入的部分才会送入或者映射到内存中。只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE,0,1024);
将文件的前1024个字节映射到内存。
2、Channel:
Channel是一个对象,可以通过它读取和写入数据。通道就像是流。
将数据写入包含一个或者多个字节的缓冲区,不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
通道与流的不同之处在于通道是双向的,而流只是在一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而通道可以用于读、写或者同时用于读写。
3、状态变量:
(1)、三个值指定缓冲区在任意时刻的状态:
position:跟踪已经写了或者读了多少数据。
limit:表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。position总是小于或者等于limit。
capacity:表明可以储存在缓冲区中的最大数据容量.limit决不能大于capacity。
另外,关于mark:
一个临时存放的位置下标。调用 mark() 会将mark设为当前的position的值,以后调用reset()会将position属性设置为mark的值。 mark的值总是小于等于position的值,如果将position的值设的比mark小,当前的mark值会被抛弃掉。
(2)、访问方法:
ByteBuffer.get() 获取字节
ByteBuffer.put() 写入字节
Buffer.clear()
重设缓冲区,使它可以接受读入的数据。它将limit设置为与capacity相同。设置position为0。
Buffer.flip()
让缓冲区可以将新读入的数据写入另一个通道。它limit设置为当前position,将position设置为0。
4、Selector、SelectableChannel和SelectionKey:
SelectableChannel:
代表了可以支持非阻塞IO操作的channel,可以将其注册在Selector上,这种注册的关系由SelectionKey这个类来表现。
SelectableChannel可以是blocking和non-blocking模式,所有channel创建的时候都是blocking模式,只有 non-blocking的SelectableChannel才可以参与非阻塞IO操作。
通过register()方法,SelectableChannel可以注册到Selector上。
ServerSocketChannel支持非阻塞操作,对应于java.net.ServerSocket这个类,提供了TCP协议IO接口,支持OP_ACCEPT操作。
socket():返回对应的ServerSocket对象。
accept():接受一个连接,返回代表这个连接的SocketChannel对象。
SocketChannel支持非阻塞操作,对应于java.net.Socket这个类,提供了TCP协议IO接口,支持OP_CONNECT,OP_READ和OP_WRITE操作。
socket():返回对应的Socket对象。
finishConnect():connect()进行一个连接操作。如果当前SocketChannel是blocking模式,这个函数会等到连接操作完成或错误发生才返回。如果当前SocketChannel是non-blocking模式,函数在连接能立刻被建立时返回true,否则函数返回false,应用程序需要在以后用finishConnect()方法来完成连接操作。
Selector:
这个类通过select() 函数,给应用程序提供了一个可以同时监控多个IO channel的方法。
应用程序通过调用select() 函数,让Selector监控注册在其上的多个SelectableChannel ,当有channel的IO操作可以进行时,select()方法就会返回以让应用程序检查channel的状态,并作相应的处理。
Selector可以同时监控多个SelectableChannel的IO状况,是非阻塞IO的核心:
在一个Selector 中,有3个SelectionKey的集合:
(1)key set代表了所有注册在这个Selector上的channel ,这个集合可以通过keys()方法拿到。
(2)Selected-key set代表了所有通过select()方法监测到可以进行IO操作的channel ,这个集合可以通过 selectedKeys()拿到。
(3)Cancelled-key set代表了已经cancel了注册关系的channel ,在下一个select()操作中,这些channel对应的SelectionKey会从key set和cancelled-key set中移走,这个集合无法直接访问。
5、其它API:
Pipe:
包含了一个读和一个写的channel(Pipe.SourceChannel 和 Pipe.SinkChannel) ,这对channel可以用于进程中的通讯。
FileChannel:
用于对文件的读、写、映射、锁定等操作,和映射操作相关的类有FileChannel.MapMode,和锁定操作相关的类有FileLock。值得注意的是FileChannel并不支持非阻塞操作。
Channels:
这个类提供了一系列static方法来支持stream类和channel类之间的互操作。这些方法可以将channel类包装为 stream类,比如,将ReadableByteChannel包装为InputStream或Reader;也可以将stream类包装为channel 类,将OutputStream包装为WritableByteChannel。
相关推荐
(3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。 (4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非...
本文主要简单介绍NIO的基本原理,在下一篇文章中,将结合Reactor模式和著名线程大师Doug Lea的一篇文章深入讨论。 NIO主要原理和适用。 NIO 有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的...
第35讲:Java NIO核心类源码解读与分析 第36讲:文件通道用法详解 第37讲:Buffer深入详解 第38讲:NIO堆外内存与零拷贝深入讲解 第39讲:NIO中Scattering与Gathering深度解析 第40讲:Selector源码深入分析 ...
Scalable IO in Java -Doug Lea 学习NIO必看经典 描述java nio 和reactor 设计模式之间的关系
【Java IO】从NIO到Reactor三种模式 博客地址:https://blog.csdn.net/qq_36963950/article/details/107998164
Scalable IO in Java -Doug Lea 描述java nio 和reactor 设计模式之间的关系
说起,然后接着阐述了阻塞和非阻塞的区别,接着介绍了阻塞IO和非阻塞IO的区别,然后介绍了同步IO和异步IO的区别,接下来介绍了5种IO模型,后介绍了两种和高性能IO设计相关的设计模式(Reactor和Proactor)。...
IO(基于选择器)+ LMAX Disruptor 需要Java 1.8。 实施的想法: select()的专用线程-React堆模式,通过特殊的WaitStrategy实现为一个中断实例。 N个线程(即处理器)处理IO事件。 一个NIO通道的处理始终在一个...
Reactor单线程模型,指的是所有的IO操作都在同一个NIO线程上面完成,NIO线程的职责如下: 1)作为NIO服务端,接收客户端的TCP连接; 2)作为NIO客户端,向服务端发起TCP连接; 3)读取通信对端的请求或者应答消息;...
基于java tcp socket通信的拆包和装包源码 功能 1)编写一个 NIO 群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞) 2)实现多人群聊 3)服务器端:可以监测用户上线,离线,并实现消息转发功能 4)客户端:...
由于React堆线程可以在执行IO时饱和,因此nioreactor使用接受器线程将新连接转发到可以在非阻塞模式下处理读取和写入的React堆池。 建筑分布 要求 2.2.0或以上 Java 8或以上 建立: git clone mvn clean install ...
学习并发编程的一些高级主题,如Java内存模型、JVM IO/NIO机制等。 在实践中学习: 在实践中学习:并发集合 在实践中学习:如何对并发应用程序进行测试。 实践学习:Java异步编程(Future、FutureTask、Guava....
Java NIO学习文档。值得学习。reactor 设计模式。作者Doug Lea。java.util.concurrent包的作者
(3)IO多路复用(IOMultiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。(4)异步IO(AsynchronousIO):即经典的Proactor设计模式,也称为异步非...
32_IO体系架构系统回顾与装饰模式的具体应用 33_Java NIO深入详解与体系分析 34_Buffer中各重要状态属性的含义与关系图解 35_Java NIO核心类源码解读与分析 36_文件通道用法详解 37_Buffer深入详解 38_NIO堆外内存与...
Java异步NIO框架Netty实现高性能高并发无标题笔记 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨 节点...
基于java tcp socket通信的拆包和装包源码 Netty-practice I/O模型分析 Netty学习实践 源码分析 BIO/NIO/AIO基础 阻塞I/O 非阻塞I/O I/O复用 信号驱动的I/O 异步I/O Java I/O模型 同步阻塞IO 1:1同步阻塞IO通信模型 ...
35_Java NIO核心类源码解读与分析 36_文件通道用法详解 37_Buffer深入详解 38_NIO堆外内存与零拷贝深入讲解 39_NIO中Scattering与Gathering深度解析 40_Selector源码深入分析 41_NIO网络访问模式分析 42_NIO网络编程...
1.BIO、NIO 和 AIO 的区别? BIO:一个连接一个线程,客户端有连接...大大提高了 Java 网络应用的可伸缩性和实用性。基于 Reactor 线程模型。 在 Reactor 模式中,事件分发器等待某个事件或者可应用或个操作的状态发