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.txt
到 some99.txt
,每个文件大小为 1Mb,这样读取速度不会太快,运行
java -Djava.nio.channels.DefaultThreadPool.threadFactory=TF AsynchFileChannelDemo
打印的数字是线程工厂创建新线程的次数。
我理解 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.txt
到 some99.txt
,每个文件大小为 1Mb,这样读取速度不会太快,运行
java -Djava.nio.channels.DefaultThreadPool.threadFactory=TF AsynchFileChannelDemo
打印的数字是线程工厂创建新线程的次数。