Java AsynchronousFileChannel - 线程使用

Java AsynchronousFileChannel - thread usage

我理解 Java 的 AsynchronousFileChannel 是一个异步 api(不阻塞调用线程)并且可以使用系统线程池中的线程。

我的问题是:AsynchronousFileChannel 操作是否具有 1:1 线程比率?

换句话说,如果一个循环使用 AsynchronousFileChannel 读取 100 个文件,它会使用 100 个线程来执行此操作还是仅使用少量线程(以标准 NIO 方式)?

AsynchronousFileChannel 一般使用的实现(例如在 Linux 上实际使用)是 SimpleAsynchronousFileChannelImpl which basically submits Runnables that do blocking IO read + process result in the same thread (either fill a future or call a CompletionHandler) to an ExecutorService which either is supplied as an argument to AsynchronousFileChannel::open, or else a default system-wide one is used (which is an unbounded cached thread pool, but has some options that can be configured). Some think 这是可以用文件完成的最好的实现,因为它们是 "always readable" 或者至少 OS 没有提供任何线索表明它们不是。

在 Windows 上使用了一个单独的实现,它被称为 WindowsAsynchronousFileChannelImpl. It uses I/O completion ports a.k.a IOCP when 运行 on Windows Vista/2008 and later (主要版本 >= "6"),并且通常表现得更像您期望的那样:默认情况下,它使用 1 个线程来分派读取结果(可由 "sun.nio.ch.internalThreadPoolSize" 系统 属性 配置)和一个缓存线程池进行处理。

因此,回答您的问题:如果您不向 AsynchronousFileChannel::open 提供自己的 ExecutorService(比如固定的),那么它将是1:1关系,所以100个文件会有100个线程;除了非古代 Windows,默认情况下将有 1 个线程处理 I/O,但如果所有结果同时到达(不太可能但仍然如此)并且您使用 CompletionHandlers,它们将被调用每个也在自己的线程中。

编辑: 我在 Linux 和 Windows (openjdk8) 上实现了读取 100 个文件和 运行 它 1) 确认类 实际上在两者上都使用了(对于那个删除 TF.class,同时仍然在命令行中指定它并查看堆栈跟踪),2)有点确认使用的线程数:[=57 上的 100 =],如果完成处理速度快,则 Windows 上为 4(如果未使用 CompletionHandlers,则相同),如果完成处理速度较慢,则为 Windows 上的 100。虽然丑陋,但代码是:

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.*;

public class AsynchFileChannelDemo {

    public static final AtomicInteger ai = new AtomicInteger();

    public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
        final List<ByteBuffer> bufs = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 100; i++) {
            Path p = Paths.get("some" + i + ".txt");
            final ByteBuffer buf = ByteBuffer.allocate(1000000);
            AsynchronousFileChannel ch = AsynchronousFileChannel.open(p, StandardOpenOption.READ);
            ch.read(buf, 0, buf, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                  bufs.add(buf);
                  // put Thread.sleep(10000) here to make it "long"
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                }
            });
        }
        if (args.length > 100) System.out.println(bufs); // never
        System.out.println(ai.get());
    }
}

import java.util.concurrent.ThreadFactory;

public class TF implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        AsynchFileChannelDemo.ai.incrementAndGet();
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
}

编译这些,把它们放在一个文件夹里,里面有 100 个文件,名为 some0.txtsome99.txt,每个文件大小为 1Mb,这样读取速度不会太快,运行

java -Djava.nio.channels.DefaultThreadPool.threadFactory=TF AsynchFileChannelDemo

打印的数字是线程工厂创建新线程的次数。