Skip to content

同步 IO vs 异步 IO

大多数人对「同步」和「异步」的理解是模糊的——只觉得「同步就是等,异步就是不等」,但说不清楚等的是什么、谁在等。

真相是:同步和异步的区别,不在于等不等,而在于数据拷贝由谁完成。

同步 IO:调用方自己干

调用 read() 时,进程从用户态进入内核态,内核把数据从磁盘拷贝到内核缓冲区,再拷贝到用户缓冲区,然后返回。整个拷贝过程调用方都在等待

java
// 同步调用:read() 返回时,数据已经在你的 byte[] 里了
byte[] data = new byte[1024];
int len = fis.read(data); // 线程在这里卡住,直到数据就绪
// 继续往下执行时,data 里已经有数据了

同步 IO 有两种状态:

  • 同步阻塞(BIO)read() 没数据就一直等
  • 同步非阻塞(NIO)read() 没数据立刻返回 -1,但有数据时仍然是你自己拷贝

异步 IO:交给别人干

调用 read()立即返回,不等待数据就绪。操作系统把数据拷贝到用户缓冲区完成后,通过回调事件通知你。

java
// JDK 7+ 异步文件 IO 示例
AsynchronousFileChannel channel = AsynchronousFileChannel.open(Path.of("data.txt"));

ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
Future<Integer> result = channel.read(buffer, 0); // 立即返回,不阻塞

// 可以干别的事
doSomethingElse();

// 等数据就绪(阻塞在这里,或者检查 Future)
Integer bytesRead = result.get();

JDK 7 的 AsynchronousFileChannel 有两种异步通知方式:

方式 1:Future 模式

java
Future<Integer> future = channel.read(buffer, 0);
while (!future.isDone()) {
    // 做别的事
}
Integer result = future.get(); // 数据就绪后才返回

方式 2:CompletionHandler 回调

java
channel.read(buffer, 0, buffer,
    new CompletionHandler<Integer, ByteBuffer>() {
        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            // 数据读完了,在这里处理
            System.out.println("读到了 " + result + " 字节");
        }

        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            // 读取失败
            exc.printStackTrace();
        }
    });

核心区别

同步 IO
调用 read() → 内核拷贝数据(你等着)→ 返回

异步 IO
调用 read()(附带回调)→ 立即返回(你可以干别的事)
                    → 内核异步拷贝数据
                    → 拷贝完成后回调通知你

Java 中的异步 IO

API说明
AsynchronousFileChanneljava.nio.channels文件异步 IO(JDK 7+)
AsynchronousSocketChanneljava.nio.channelsSocket 异步 IO(JDK 7+)
AsynchronousServerSocketChanneljava.nio.channels服务端异步 Socket(JDK 7+)

注意:异步 IO 在 Java 中性能提升并不显著,实际用得不多。生产环境更多用 NIO + 多路复用或直接上 Netty。

最后送你一句话:同步和异步的区别,不在于等不等,而在于拷贝由谁完成。

下一步

基于 VitePress 构建