Java 多线程文件下载性能

2022-09-02 22:23:36

我最近在一个需要比我习惯的更多的IO交互的项目上工作,我觉得我想超越常规库(特别是Commons IO),并解决一些更深入的IO问题。

作为学术测试,我决定实现一个基本的多线程HTTP下载器。这个想法很简单:提供一个URL下载,代码将下载文件。为了提高下载速度,文件被分块,每个区块同时下载(使用HTTP标头)以使用尽可能多的带宽。Range: bytes=x-x

我有一个工作原型,但正如你可能已经猜到的那样,它并不完全理想。目前,我手动启动3个“下载器”线程,每个线程下载1/3的文件。这些线程使用通用的同步“文件编写器”实例将文件实际写入磁盘。完成所有线程后,“文件编写器”完成,并关闭所有打开的流。一些代码片段可以给你一个想法:

线程启动:

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;
}

“文件编写器”使用 to 和块到磁盘写入磁盘:RandomAccessFileseek()write()

public synchronized void write(byte[] bytes, int len, long start) throws IOException
{
      output.seek(start);
      output.write(bytes, 0, len);
}

考虑到所有因素,这种方法似乎有效。但是,它不能很好地工作。我很感激以下几点的建议/帮助/意见。非常感谢。

  1. 此代码的 CPU 使用率是经过屋顶的。它使用我的一半CPU(2个内核中每个内核的50%)来做到这一点,这比类似的下载工具成倍地多,这些工具几乎没有给CPU带来任何压力。我对这种CPU使用率的来源有点困惑,因为我没想到这一点。
  2. 通常,3个线程中似乎有1个明显滞后。其他 2 个线程将完成,之后需要 30 秒或更长时间才能完成第三个线程(似乎主要是具有第一个块的第一个线程)。我可以从任务管理器中看到javaw进程仍在进行小的IO写入,但我真的不知道为什么会发生这种情况(我猜是竞争条件?)。
  3. 尽管我选择了相当大的缓冲区(1MB),但我感觉几乎从未真正填充缓冲区,这会导致比我想要的更多的IO写入。我的印象是,在这种情况下,最好将IO访问保持在最低限度,但我不确定这是否是最好的方法。InputStream
  4. 我意识到Java可能不是做这样的事情的理想语言,但我相信,在我目前的实现中,它的性能要比我想象的要高得多。在这种情况下,蔚来汽车值得探索吗?

注意:我使用Apache HTTPClient来做HTTP交互,这是它的来源(以防有人想知道)。entity.getContent()


答案 1

回答我自己的问题:

  1. CPU 使用率增加是由于循环等待线程完成。事实证明,等待一个完成:)while() {}awaitTerminationExecutor
  2. (以及 3 和 4)这似乎是野兽的本性;最后,我通过仔细同步每个下载一大块数据的不同线程(特别是将这些块写回磁盘)实现了我想做的事情。

答案 2

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


推荐