JAVA IO模型
常见的IO模型有:阻塞IO模型、非阻塞IO模型、多路复用IO模型、信号驱动IO模型、异步IO模型。
一、阻塞IO模型
阻塞IO模型是最传统的一种IO模型,就是在读写数据过程中会发生阻塞现象。当用户线程发出IO请求后,内核就会去查看数据是否准备就绪,如果没有就绪,就会等待数据就绪,此时的用户线程就处于阻塞状态。当数据就绪之后,内核就会将数据拷贝到用户线程,并返回结果给用户线程,用户线程解除block状态。
二、非阻塞IO模型
当用户线程发起一个IO请求后(如read操作),并不需要等待,而是马上得到一个结果,如果结果是error,此时用户线程就知道数据还没有准备好,接着用户线程会继续发送read操作,直到内核中的数据准备好,用户线程又发送了read操作之后,那么内核就会马上把数据拷贝到用户线程,并返回结果。非阻塞IO模型并不会阻塞线程,但是会不断地询问内核数据是否就绪,持续占用CPU,导致CPU占用率非常高,一般情况下很少使用。
// 典型的非阻塞IO模型 while(true){ data = socket.read(); if(data!= error){ // 处理数据 break; } }
三、多路复用IO模型
java中的NIO实际上就是多路复用IO。多路复用IO模型中,会有一个线程不断地去轮询多个socket的状态,只有socket发出读写请求时,才会有真正的IO读写操作。
java NIO的三大核心部分:
-
Buffer(缓冲区):每个客户端连接都会对应一个Buffer,读写数据通过缓冲区读写。
-
Channel(通道):每个Channel用于连接Buffer和Selector,通道可以及逆行双向读写。
-
Selector(选择器):一个选择器可以对应多个通道,用于监听多个通道的事件。Selector可以监听所有Channel是否有数据需要读取,当某个Channel有数据时,就去处理,所有的Channel都没有数据时,线程可以去执行其他任务。
1、Buffer
public static void main(String[] args) { // 创建一个int类型的Buffer,大小为5 IntBuffer buffer = IntBuffer.allocate(5); // 往buffer中添加数据 for (int i = 0; i < buffer.capacity(); i++) { buffer.put(i*2); } // buffer读写切换 buffer.flip(); // 读取buffer中的数据 while (buffer.hasRemaining()){ System.out.println(buffer.get()); } }
使用最多的是ByteBuffer,因为网络传输中一般使用字节传输。
2、Channel
NIO的Channel通道类似于流,但是通道可以同时读写,而流只能读或写。
Channel只是一个接口,里面有各种实现类。
通过FileChannel和ByteBuffer将数据写入文件。
public static void main(String[] args) throws IOException { // 创建一个文件输出流fileOutputStream FileOutputStream fileOutputStream = new FileOutputStream("test.txt"); // 通过fileOutputStream获取一个channel FileChannel channel = fileOutputStream.getChannel(); // 创建一个Buffer ByteBuffer byteBuffer = ByteBuffer.allocate(1024); byteBuffer.put("weiweiwei".getBytes()); byteBuffer.flip(); // 将指针指向数组开头 // 将Buffer中的数据写入channel中 channel.write(byteBuffer); fileOutputStream.close(); }
3、Selector
Selector能够检测多个注册的通道上是否有事件发生(多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件,然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。
过程:
-
当客户端连接时,会通过ServerSocketChannel得到SocketChannel。
-
将SocketChannel注册到Selector上,一个Selector可以注册多个SocketChannel。
-
注册后会返回一个SelectionKey,会和该Selector关联(加入到集合中)。
-
Selector进行监听select方法,返回有事件发生的通道的个数。
-
进一步得到各个有事件发生的SelectionKey。
-
通过SelectionKey反向获取SocketChannel,然后获取Channel的事件类型,并处理Selector通过管理SelectionKey的集合从而去监听各个Channel。
四、信号驱动IO模型
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行IO请求操作。
五、异步IO模型
异步IO模型中当用户线程发起一个read操作之后,并不需要阻塞,可以立即去干其他的事情。与此同时,当内核收到一个asynchronous read请求时,会立刻给用户线程返回一个消息,告诉用户线程已经接收到read请求了,然后内核就会等待数据就绪。当数据准备就绪之后内核就把数据拷贝到用户线程。然后会给用户线程发送一个信号,告诉用户线程read操作已经成功完成。此时用户线程就可以直接使用数据。与信号驱动IO模型不同的是,异步IO模型根本不用在一整个IO操作是怎样执行的,只需要向内核发送请求,等待内核返回操作成功的信号,就可以直接使用数据。而信号驱动IO模型需要自己去在信号函数中调用IO读写操作来进行IO请求操作。