CodeGym /课程 /JAVA 25 SELF /Future、CompletionHandler:处理操作完成

Future、CompletionHandler:处理操作完成

JAVA 25 SELF
第 56 级 , 课程 1
可用

1. Future:“等它准备好”

Java 中的异步操作就像寄出一封信:你不会守在信箱前等待回复,而是去做自己的事。等信到了——你只需要接收通知。在代码中,你需要知道操作何时完成,并获取结果或错误。在 Java 中有两种方式:通过 Future,以及通过 CompletionHandler

使用 Future 时,你会得到一张“回执”——对未来结果的承诺。你可以不时检查它是否已就绪,或者调用 get() 来等待结果。

使用 CompletionHandler 时,你甚至无需等待:预先提供一个处理器,成功或失败时它都会被自动调用。

它是如何工作的?

当你在 AsynchronousFileChannel 上调用异步的 read()write() 方法时,你可以得到一个 Future<Integer> 对象。这就像电子排队的小票:操作已经开始,你可以随时问一句:“好了没有?”。

方法签名

Future<Integer> read(ByteBuffer dst, long position)

Future<Integer> write(ByteBuffer src, long position)
  • dst — 读取数据的目标缓冲区。
  • src — 写入数据的源缓冲区。
  • position — 文件中的读/写位置。

示例:使用 Future 的异步读取

以下代码异步读取文件的前 1024 字节,并显式等待结果:

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;
import java.io.IOException;

public class FutureReadExample {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // 启动异步读取
            Future<Integer> future = channel.read(buffer, 0);

            // ... 在文件读取期间可以做其他事情

            // 等待操作完成(阻塞调用!)
            int bytesRead = future.get(); // 可能抛出 InterruptedException、ExecutionException

            System.out.println("已读取字节数: " + bytesRead);

            // 开始从缓冲区读取
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
        } catch (Exception e) {
            System.err.println("文件读取错误: " + e);
        }
    }
}
  • future.get() 会阻塞线程直到操作完成——就像“守着电话”等对方来电。
  • 可以先调用 future.isDone() 来判断操作是否完成,再调用 get()
  • 如果操作以错误结束,get() 会抛出 ExecutionException(其内部包含原因)。

什么时候适合使用 Future?

当你想启动任务并在合适的时机等待结果时,Future 很好用:读取文件、发起请求、在后台计算等。它也适合并行启动多个操作后再统一汇总结果。

但如果你需要真正的无阻塞异步,并能在完成时自动通知,那么应考虑使用 CompletionHandler

2. CompletionHandler:“完成后叫我”

CompletionHandler 是一个接口,你实现它并将其实例传给 read()write()。当操作完成(或发生错误)时,Java 会自动调用你的处理器。这就像留下电话号码:“准备好后请来电”。

方法签名

void read(ByteBuffer dst,
          long position,
          A attachment,
          CompletionHandler<Integer, ? super A> handler)
  • dst — 读取用的缓冲区。
  • position — 文件中的位置。
  • attachment — 将传递给处理器的任意对象(可以是 null,也可以是例如文件名)。
  • handler — 你的处理器。

CompletionHandler 接口

public interface CompletionHandler<V, A> {
    void completed(V result, A attachment);
    void failed(Throwable exc, A attachment);
}
  • completed 在成功时调用;result 是字节数,attachment 是你的对象。
  • failed 在出错时调用;exc 是异常。

示例:使用 CompletionHandler 的异步读取

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.io.IOException;

public class CompletionHandlerReadExample {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("example.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        channel.read(buffer, 0, "example.txt", new CompletionHandler<Integer, String>() {
            @Override
            public void completed(Integer result, String attachment) {
                System.out.println("文件 " + attachment + " 已读取,字节数: " + result);
                buffer.flip();
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                closeChannel();
            }

            @Override
            public void failed(Throwable exc, String attachment) {
                System.err.println("读取文件 " + attachment + " 时出错: " + exc);
                closeChannel();
            }

            private void closeChannel() {
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("关闭通道时出错: " + e);
                }
            }
        });

        // 重要:如果不"阻止"线程,main 方法可能会在读取完成前结束!
        // 在真实应用中,线程通常不会立刻结束(例如服务器、UI)。
        try {
            Thread.sleep(500); // 给异步操作留点时间(仅示例!)
        } catch (InterruptedException ignored) {}
    }
}

我们向 read() 传入了一个匿名处理器,它知道在完成后该做什么。在 completed 中获取结果并处理,在 failed 中对错误作出响应。别忘了在处理器内部关闭通道,避免资源泄漏。

有个细节:main 方法可能会先于异步操作结束而结束,因此示例里添加了 Thread.sleep(500)——只是为了看到结果。在真实应用中通常不需要这样的技巧。

什么时候更适合使用 CompletionHandler?

当你需要真正的无等待、无阻塞的异步时,CompletionHandler 更合适:启动操作并描述完成后的处理。这对 UI(JavaFX、Swing)至关重要,避免界面“卡死”;对服务器也很有用——线程不必空等,只在有工作时才被占用。

4. Future 与 CompletionHandler 的对比

方式 是否阻塞线程? 何时使用? 示例场景
Future 是(调用 get() 时) 简单的顺序处理 文件拷贝、报表
CompletionHandler 真正的异步、UI、服务器、并行操作 服务器、GUI、大量 IO
  • Future 更易理解,但获取结果需要阻塞。
  • CompletionHandler 稍微复杂些,但提供真正的异步且不阻塞线程。

5. 实战:使用 CompletionHandler 异步写入文件

一个示例,异步将字符串写入文件并报告结果:

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.io.IOException;
import java.nio.charset.StandardCharsets;

public class CompletionHandlerWriteExample {
    public static void main(String[] args) throws IOException {
        String text = "你好,异步的世界!";
        ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
        Path path = Path.of("async_output.txt");

        AsynchronousFileChannel channel = AsynchronousFileChannel.open(
                path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        channel.write(buffer, 0, path, new CompletionHandler<Integer, Path>() {
            @Override
            public void completed(Integer result, Path attachment) {
                System.out.println("成功写入 " + result + " 字节到文件 " + attachment);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("关闭通道时出错: " + e);
                }
            }

            @Override
            public void failed(Throwable exc, Path attachment) {
                System.err.println("写入文件 " + attachment + " 时出错: " + exc);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("关闭通道时出错: " + e);
                }
            }
        });

        // 暂停 main 以便看到结果(仅示例)
        try {
            Thread.sleep(500);
        } catch (InterruptedException ignored) {}
    }
}
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION