Java多线程文件下载性能

Posted

技术标签:

【中文标题】Java多线程文件下载性能【英文标题】:Java multithreaded file downloading performance 【发布时间】: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;

“文件写入器”使用RandomAccessFileseek()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() 的来源(以防有人想知道)。

【问题讨论】:

在这里找到了一个很好的相关主题:***.com/questions/921262/… 今晚回家后可能会尝试一下:) 更新:高 CPU 使用率是由于 ExecutorService isTerminated() 方法调用上的 while() 循环造成的。哇! 我认为很大程度上取决于网络配置以及网络接口卡(物理)。即使您有多个线程在下载同一个文件,但负责序列化字节的 NIC 可能会成为瓶颈! 【参考方案1】:

回答我自己的问题:

    CPU 使用率增加是由于 while() 循环正在等待线程完成。事实证明,awaitTermination 是等待Executor 完成的更好选择:) (以及 3 和 4)这似乎是野兽的本性;最后,我通过仔细同步不同线程来实现我想做的事情,每个线程都下载一个数据块(嗯,特别是这些块写回磁盘)。

【讨论】:

【参考方案2】:

大概 Apache HTTP 客户端将使用较小的缓冲区进行一些缓冲。它将需要一个缓冲区来合理地读取 HTTP 标头,并可能处理分块编码。

【讨论】:

【参考方案3】:

我在 Windows 上获得最佳性能的直接想法是使用 IO completions ports。我不知道的是(a)其他操作系统中是否有类似的概念,以及(b)是否有合适的 Java 包装器?不过,如果可移植性对您来说并不重要,那么您可以使用 JNI 推出自己的包装器。

【讨论】:

【参考方案4】:

设置一个非常大的套接字接收缓冲区。但实际上,您的性能将受到网络带宽的限制,而不是 CPU 带宽。您实际上所做的只是将 1/3 的网络带宽分配给每个下载器。如果你得到很多好处,我会感到惊讶。

【讨论】:

三个连接可能比一个快,在传输开始时短暂。 TCP 需要花一点时间才能找到最佳窗口大小,所以如果使用并行连接,这个过程会快 3 倍! 这就是我首先进行分块的原因,将文件分成 3 个块可以让我下载同一个文件的速度快 3 倍,假设单个块太慢而无法使连接饱和。但是下载速度已经达到最大值,所以这不是问题。我担心的是CPU使用率。会不会与代码在 Eclipse 中执行有关? 好吧,您正在执行 N-1 次冗余搜索,而我上次查看它时,那是几十年前,搜索是一项非常昂贵的操作。每个作家只需要寻找一次;之后它只是顺序 I/O。

以上是关于Java多线程文件下载性能的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程文件下载性能

jAVA基础 提高文件复制性能之多线程复制文件

java多线程

Java多线程性能优化

Java:Java多线程实现性能测试

Java:Java多线程实现性能测试