JAVA IO模型

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的三大核心部分:

  1. Buffer(缓冲区):每个客户端连接都会对应一个Buffer,读写数据通过缓冲区读写。

  2. Channel(通道):每个Channel用于连接Buffer和Selector,通道可以及逆行双向读写。

  3. 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),如果有事件发生,便获取事件,然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。

只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。

过程:

  1. 当客户端连接时,会通过ServerSocketChannel得到SocketChannel。

  2. 将SocketChannel注册到Selector上,一个Selector可以注册多个SocketChannel。

  3. 注册后会返回一个SelectionKey,会和该Selector关联(加入到集合中)。

  4. Selector进行监听select方法,返回有事件发生的通道的个数。

  5. 进一步得到各个有事件发生的SelectionKey。

  6. 通过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请求操作。

你可能感兴趣的:(java,开发语言)