最近,我完成了一个项目,该项目比以前需要更多的IO交互,我觉得我想超越常规库(尤其是Common 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;
}
“文件编写器”使用RandomAccessFileto seek()和write()将大块写入磁盘:
public synchronized void write(byte[] bytes, int len, long start) throws IOException
{
output.seek(start);
output.write(bytes, 0, len);
}
考虑所有事情,这种方法似乎行得通。但是,它不能很好地工作。对于以下几点,我将不胜感激。非常感激。
此代码的 CPU使用率 是通过屋顶计算的。它使用了我一半的CPU(2个内核中的每个内核的50%)来执行此操作,这比类似的下载工具成倍增加,后者几乎没有给CPU带来压力。我对这种CPU使用量的来源有些怀疑,因为我没想到这一点。
通常,似乎3个线程中有1个明显 滞后 。其他2个线程将完成,此后,第三个线程(似乎主要是带有第一个块的第一个线程)需要30秒钟或更长时间才能完成。我从任务管理器中可以看到javaw进程仍在进行较小的IO写入,但是我真的不知道为什么会发生这种情况(我正在猜测竞争条件?)。
尽管我选择了一个很大的缓冲区(1MB),但我仍然感到InputStream几乎从来没有真正填充过缓冲区,这会导致IO写操作比我想要的更多。我的印象是,在这种情况下,最好将IO访问保持在最低水平,但是我不确定这是否是最好的方法。
我意识到Java可能不是执行此类操作的理想语言,但是我坚信要拥有比当前实现更多的性能。在这种情况下,NIO是否值得探索?
注意: 我使用Apache HTTPClient进行HTTP交互,这就是它的来历entity.getContent()(以防万一有人想知道)。