Javamultithreading文件下载性能

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

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

我有一个工作原型,但你可能已经猜到了,它并不完全理想。 目前我手动启动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; } 

“文件编写器”使用RandomAccessFile写入磁盘以seek()并将块seek() write()磁盘:

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

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

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

注意:我使用Apache HTTPClient进行HTTP交互,这是entity.getContent()来源(万一有人想知道)。

回答我自己的问题:

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

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

我想在Windows上获得最佳性能的想法是使用IO完成端口 。 我不知道的是(a)其他操作系统中是否有类似的概念,以及(b)是否有任何合适的Java包装器? 但是,如果可移植性对您不重要,则可以使用JNI滚动自己的包装器。

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