BIO NIO AIO

BIO,NIO,AIO 对应三种不同的 IO 模型,本文将详细介绍这三种 IO 模型。

同步和异步

  • 同步: 任务顺序执行,必须等前一个任务完成后才可以继续执行后面的任务
  • 异步: 任务发起后,不用等待任务完成,可以忙其他的任务,当任务完成时,会主动进行通知

同步和异步主要的区别在于任务完成时是否有通知(回调)机制,如果没有所谓的通知(回调)机制,那就是同步的,如果有通知(回调)机制,那就是异步。不能够通过是否阻塞来判断是同步还是异步

代码示例

// 同步调用,readFile 方法执行时阻塞等待文件读完
String data = readFile("file.txt");
System.out.println("文件内容: " + data);
  • readFile 没有读取完毕之前,程序不会往下执行
// 异步调用,方法立即返回
readFileAsync("file.txt", new Callback() {
    @Override
    void onComplete(String data) {
        System.out.println("文件内容: " + data);
    }
});

System.out.println("我不用等文件读完,先去干别的事");
  • readFileAsync 调用后立即返回,不阻塞
  • 文件读取完毕后,调用 onComplete 通知读取的结果

阻塞和非阻塞

阻塞与非阻塞指的是线程的状态。

  • 阻塞: 指的是 I/O 操作的这段时间,线程处于阻塞状态,等待 I/O 操作完成后唤醒线程并继续往下执行
  • 非阻塞: 指的是 I/O 操作的这段时间,线程处于非阻塞(运行态)状态。线程发起 I/O 操作,发现数据不可用时,立即返回,不等待, 继续向下执行。

代码示例

// 同步调用,readFile方法执行时阻塞等待文件读完
String data = readFile("file.txt"); // 阻塞等待 IO 完成
System.out.println("文件内容: " + data);
  • 执行 readFile 时,线程处于阻塞状态,等待 IO 完成
FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);

// 不阻塞,直接向下执行
int bytesRead = fileChannel.read(buffer); 

// 由于没有回调(通知)机制,只能够不断的轮询
while (bytesRead != -1) {
	buffer.flip();
	System.out.println("读取的内容: " + StandardCharsets.UTF_8.decode(buffer));
	buffer.clear();
	bytesRead = fileChannel.read(buffer);
}

fileChannel.close();
  • 执行 read 方法的时候,如果没有数据,直接返回 0,不阻塞等待
  • 因为没有回调(通知)机制,只能够通过 while 循环来判断 IO 操作是否完成

BIO

Blocking I/O(BIO),是一种同步阻塞的 IO 模型。线程执行 IO 操作时会进入阻塞状态,直到 IO 操作完成时,被唤醒再继续往下执行。线程与 IO 操作的关系是一对一,也就是一个线程对应一个 IO 操作

image-20251029171138914

代码示例

import java.io.FileInputStream;
import java.io.IOException;

public class BioFileReadExample {
    public static void main(String[] args) {
        String filePath = "test-bio.txt";

        try (FileInputStream fis = new FileInputStream(filePath)) {
            byte[] buffer = new byte[1024];
            int bytesRead;

            // 阻塞式读取,read()会阻塞直到有数据或文件结束
            while ((bytesRead = fis.read(buffer)) != -1) {
                String content = new String(buffer, 0, bytesRead, "UTF-8");
                System.out.print(content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

NIO

Non-Blocking I/O(NIO), 是一种同步非阻塞的 IO 模型。线程发起 IO 操作后,不会进入阻塞状态等待 IO 操作的完成,而是直接往下继续执行。线程与 IO 操作的关系是一对多,也就是一个线程可以管理多个 IO 操作。

NIO 相较于 BIO 多了一个选择器(Selector)组件,由选择器来管理多个 IO 操作,线程直接与选择器进行对话。

image-20251029172103131

代码示例

// NIO 服务器示例,非阻塞IO,单线程可同时处理多个连接
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);  // 非阻塞模式

Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select();  // 阻塞,等待某个通道准备好事件(连接读写等)

    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> iter = selectedKeys.iterator();

    while (iter.hasNext()) {
        SelectionKey key = iter.next();
        iter.remove();

        if (key.isAcceptable()) {  // 有新连接
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            SocketChannel clientChannel = ssc.accept();
            clientChannel.configureBlocking(false);
            clientChannel.register(selector, SelectionKey.OP_READ);
            System.out.println("Accepted new connection");
        } else if (key.isReadable()) {  // 有数据读
            SocketChannel clientChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = clientChannel.read(buffer);
            if (bytesRead == -1) {
                clientChannel.close();
            } else {
                buffer.flip();
                byte[] data = new byte[buffer.limit()];
                buffer.get(data);
                System.out.println("Received: " + new String(data));
            }
        }
    }
}

AIO

Asynchronous I/O(AIO), 是一种异步非阻塞的 IO 模型。线程发起 IO 操作后,不会等待 IO 操作的完成,而是继续往下执行,但是当 IO 操作完成后,会主动通知线程 IO 操作完毕。相较于 NIO 模型,加上了一个主动通知的功能,避免了轮训带来的性能损耗。

代码示例

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;

public class AioFileExample {

    public static void main(String[] args) throws Exception {
        String filePath = "test-aio.txt";

        // 计数器,等待异步操作完成
        CountDownLatch latch = new CountDownLatch(2);

        // 异步写入示例
        asynchronousWrite(filePath, "Hello AIO World!\n", latch);

        // 等待写入完成,再开始读取(简化处理,实际可用回调链式调用)
        latch.await();

        // 重置计数器用于读取
        latch = new CountDownLatch(1);

        // 异步读取示例
        asynchronousRead(filePath, latch);

        // 等待读取完成
        latch.await();

        System.out.println("所有异步操作完成");
    }

    // 异步写入文件
    public static void asynchronousWrite(String filePath, String content, CountDownLatch latch) throws IOException {
        AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
                Path.of(filePath),
                StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        ByteBuffer buffer = ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8));

        // 异步写,写完调用回调
        fileChannel.write(buffer, 0, null, new CompletionHandler<Integer, Void>() {
            @Override
            public void completed(Integer result, Void attachment) {
                System.out.println("写入完成,字节数: " + result);
                try {
                    fileChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                latch.countDown();  // 计数减1,表示写入完成
            }

            @Override
            public void failed(Throwable exc, Void attachment) {
                System.err.println("写入失败: " + exc);
                latch.countDown();
                try {
                    fileChannel.close();
                } catch (IOException e) { e.printStackTrace();}
            }
        });
    }

    // 异步读取文件
    public static void asynchronousRead(String filePath, CountDownLatch latch) throws IOException {
        AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
                Path.of(filePath),
                StandardOpenOption.READ);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 异步读,读完调用回调
        fileChannel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                System.out.println("读取完成,字节数: " + result);
                attachment.flip();
                byte[] data = new byte[attachment.limit()];
                attachment.get(data);

                String content = new String(data, StandardCharsets.UTF_8);
                System.out.println("文件内容: \n" + content);
                try {
                    fileChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                latch.countDown();  // 计数减1,表示读取完成
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                System.err.println("读取失败: " + exc);
                latch.countDown();
                try {
                    fileChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
使用 Hugo 构建
主题 StackJimmy 设计