【发布时间】:2011-03-25 11:43:29
【问题描述】:
最近参与了一个需要比以往更多的 IO 交互的项目,我觉得我想超越常规库(尤其是 Commons IO)并解决一些更深入的 IO 问题。
作为一项学术测试,我决定实现一个基本的多线程 HTTP 下载器。这个想法很简单:提供一个 URL 来下载,代码将下载文件。为了提高下载速度,文件被分块并同时下载每个块(使用 HTTP Range: bytes=x-xheader)以使用尽可能多的带宽。
我有一个工作原型,但正如您可能已经猜到的那样,它并不完全理想。目前我手动启动 3 个“下载器”线程,每个线程下载文件的 1/3。这些线程使用一个通用的、同步的“文件写入器”实例将文件实际写入磁盘。当所有线程都完成后,“文件写入器”就完成了,所有打开的流都被关闭了。一些sn-ps的代码给你一个想法:
线程启动:
ExecutorService downloadExecutor = Executors.newFixedThreadPool(3);
...
downloadExecutor.execute(new Downloader(fileWriter, download, start1, end1));
downloadExecutor.execute(new Downloader(fileWriter, download, start2, end2));
downloadExecutor.execute(new Downloader(fileWriter, download, start3, end3));
每个“下载器”线程下载一个块(缓冲)并使用“文件写入器”写入磁盘:
int bytesRead = 0;
byte[] buffer = new byte[1024*1024];
InputStream inStream = entity.getContent();
long seekOffset = chunkStart;
while ((bytesRead = inStream.read(buffer)) != -1)
{
fileWriter.write(buffer, bytesRead, seekOffset);
seekOffset += bytesRead;
}
“文件写入器”使用RandomAccessFile 到seek() 和write() 将块写入磁盘:
public synchronized void write(byte[] bytes, int len, long start) throws IOException
{
output.seek(start);
output.write(bytes, 0, len);
}
考虑到所有因素,这种方法似乎有效。但是,它的效果不是很好。对于以下几点,我将不胜感激一些建议/帮助/意见。非常感谢。
- 此代码的 CPU 使用率 已经达到了顶峰。它使用了我一半的 CPU(两个内核各占 50%)来执行此操作,这比几乎不会对 CPU 造成压力的同类下载工具成倍增加。我对这个 CPU 使用率的来源有点迷惑,因为我没想到会这样。
- 通常,3 个线程中似乎有 1 个线程明显落后。其他 2 个线程将完成,之后第三个线程(似乎主要是第一个线程的第一个线程)需要 30 秒或更长时间才能完成。我可以从任务管理器中看到 javaw 进程仍在进行小 IO 写入,但我真的不知道为什么会发生这种情况(我猜是竞态条件?)。
- 尽管我选择了相当大的缓冲区 (1MB),但我感觉
InputStream几乎从未真正填满缓冲区,这导致 IO 写入超出我的预期。我的印象是,在这种情况下,最好将 IO 访问保持在最低限度,但我不确定这是否是最佳方法。 - 我意识到 Java 可能不是执行此类操作的理想语言,但我确信与我当前的实现相比,它的性能要高得多。在这种情况下,蔚来汽车值得探索吗?
注意:我使用 Apache HTTPClient 进行 HTTP 交互,这就是 entity.getContent() 的来源(以防有人想知道)。
【问题讨论】:
-
在这里找到了一个很好的相关主题:stackoverflow.com/questions/921262/… 今晚回家后可能会尝试一下:)
-
更新:高 CPU 使用率是由于 ExecutorService isTerminated() 方法调用上的 while() 循环造成的。哇!
-
我认为很大程度上取决于网络配置以及网络接口卡(物理)。即使您有多个线程在下载同一个文件,但负责序列化字节的 NIC 可能会成为瓶颈!
标签: java performance multithreading http io