多线程从多个线程写入同一文件而不锁定,Java
我正在制作一个下载管理器,我希望多个线程下载文件的不同部分,以便一次在不同的位置写入文件。对于每个人的澄清,我不希望文件被锁定,因为它会扼杀不同线程一次写入的目的。我正在使用Apache HttpClient库和FileChannel transferFrom()。当前代码只下载第一段,而忽略其他段
代码说明: startDownload方法创建一个新文件并检查链接是否支持部分内容,如果支持,则为每个片段启动线程,否则单个线程将下载整个文件。getFileName是从URI提取文件名的函数。Download方法包含使用HttpClient实际下载文件,然后使用transferFrom写入的代码
public void startDownload() {
Thread thread = new Thread(() -> {
try {
String downloadDirectory = "/home/muhammad/";
URI uri = new URI("http://94.23.204.158/JDownloader.zip");
int segments = 2;
// Create a HttpClient for checking file for segmentation.
CloseableHttpClient Checkingclient = HttpClients.createDefault();
// get request for checking size of file.
HttpGet checkingGet = new HttpGet(uri);
CloseableHttpResponse checkingResponse = Checkingclient.execute(checkingGet);
long sizeofFile = checkingResponse.getEntity().getContentLength();
// Create a new file in downloadDirectory with name extracted from uri.
File file = new File(downloadDirectory + getFileName(uri));
if (!file.exists()) {
file.createNewFile();
}
// set range header for checking server support for partial content.
checkingGet.setHeader("Range", "bytes=" + 0 + "-" + 1);
checkingResponse = Checkingclient.execute(checkingGet);
// Check if response code is 206 (partial content response code).
if (checkingResponse.getStatusLine().getStatusCode() == 206) {
//find size of each segment.
final long sizeOfEachSegment = sizeofFile / segments;
//Download each segment independently.
for (int i = 0; i < segments; i++) {
Download(i * sizeOfEachSegment, (i + 1) * sizeOfEachSegment, sizeOfEachSegment, file, uri);
}
// Thread used for last few Bytes and EOF.
Download(sizeOfEachSegment * segments, sizeofFile, Long.MAX_VALUE, file, uri);
} else {
System.err.println("server dont support partial content");
System.out.println(checkingResponse.getStatusLine().getStatusCode());
// Download complete file using single thread.
Download(0, 0, Long.MAX_VALUE, file, uri);
}
} catch (IOException | URISyntaxException ex) {
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
}
});
thread.start();
}
public void Download(long start, long end, long sizeOfEachSegment, File file, URI uri) {
Thread thread = new Thread(() -> {
try {
FileChannel fileChannel = new FileOutputStream(file).getChannel();
CloseableHttpClient client = HttpClients.createDefault();
HttpGet get = new HttpGet(uri);
// Range header for defining which segment of file we want to receive.
if (end != 0) {
String byteRange = start + "-" + end;
get.setHeader("Range", "bytes=" + byteRange);
}
CloseableHttpResponse response = client.execute(get);
ReadableByteChannel inputChannel = Channels.newChannel(response.getEntity().getContent());
fileChannel.transferFrom(inputChannel, start, sizeOfEachSegment);
response.close();
client.close();
fileChannel.close();
} catch (IOException | IllegalStateException exception) {
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, exception);
}
});
thread.start();
}
对现有代码进行一些修复,可以让多个线程在不等待的情况下同时写入同一个文件,这将是一件好事,但如果它们能够完成上述任务,我还对研究其他更有效的技术感兴趣。如果在任何情况下,不等待就写入文件是不可能的,那么任何其他有效的解决方案都是受欢迎的。提前感谢:)
# 1 楼答案
从不同的线程写入同一个文件对您毫无帮助,甚至可能会极大地降低吞吐量
您应该使用one线程写入文件并从队列中馈送它
比如:
现在,每个下载线程都应该从下载中读取一个块,创建一个
WriteBlock
并将其发布到队列中同时,写入线程将
WriteBlock
从队列中抽出,并尽可能快地写入它们在队列中(可能使用PriorityBlockingQueue)可能会对块重新排序进行优化,但首先要以简单的方式进行
# 2 楼答案
与多个线程写入同一文件不同,您可以让一个线程写入该文件,多个线程生成数据,但将其存储在文件编写器线程的某种缓冲区中