【问题标题】:write from multiple threads on same file without locking, Java从多个线程写入同一个文件而不锁定,Java
【发布时间】:2014-07-23 19:22:27
【问题描述】:

我正在制作一个下载管理器,我希望多个线程下载不同的文件段,以便一次写入不同位置的文件。只是为了每个人的澄清,我不希望文件被锁定,因为它会杀死不同线程一次写入的目的。我正在使用 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();
}

对现有代码的一些修复可以使多个线程同时写入同一个文件而无需等待会很好,但我也有兴趣研究其他更有效的技术,如果他们可以完成上述任务。如果在任何情况下不等待就写入文件是不可能的,那么任何其他有效的解决方案都更受欢迎。在此先感谢:)

【问题讨论】:

  • 如果你从 one 源下载,我看不出并行下载有什么好处。网络可能会比本地 IO 慢得多。
  • @Tichodroma:感谢您的评论,您是绝对正确的,您能否指出任何使用从不同线程读取的数据立即写入单个而不创建多个文件或等待的方法。

标签: java multithreading apache-httpclient-4.x filechannel


【解决方案1】:

您可以让一个线程写入文件,而不是多个线程写入同一个文件,多个线程生成数据但将其存储在文件写入线程的某种缓冲区中。

【讨论】:

  • 谢谢 NESPowerGlove 你能指导我在这种情况下如何做到这一点。
  • 创建一个 BlockingQueue,将它传递给 FileWriter 线程,以及每个数据生成线程。在线检查生产者消费者问题,这就是你所拥有的。您可以使用 ExecutorService 使这变得非常简单。
  • 感谢搜索,只是为了扼杀我的好奇心,是否有任何可能的方法可以直接从多个线程写入单个文件。我不明白为什么 java 不允许我打开多个输出流到同一个文件并并行写入不同的非重叠位置。
  • 我相信这是因为您在 IO 方面受到限制。您的硬盘一次只能写入一件事。还是您在谈论抱怨文件已被用于写入的异常?
  • 这没有提供问题的答案。要批评或要求作者澄清,请在其帖子下方发表评论。
【解决方案2】:

从不同的线程写入同一个文件对您毫无帮助 - 它甚至可能会极大地损害吞吐量。

您应该使用 一个 线程来写入文件并从队列中提供它。

类似:

class WriteBlock {
    long offset;
    byte[] data;
}
BlockingQueue<WriteBlock> writeQueue = new LinkedBlockingQueue<>();

现在每个下载线程应该从下载中读取一个块,创建一个WriteBlock 并将其发布到队列中。

与此同时,写入线程将WriteBlocks 从队列中吸出并尽可能快地写入它们。

可能有一些优化可以在队列中重新排序块(可能使用PriorityBlockingQueue),但首先要以简单的方式进行。

【讨论】:

  • 现在我在队列中有数据,如何使用文件通道 transferFrom 函数将这些数据写入文件。 transferFrom 需要 readableByteChannel 任何想法如何为队列获取 readableByteChannel 或 outputStream?
  • @user3804236 - 您可以将byte[] 包装在ByteArrayInputStream 中,然后使用Channels.newChannel(InputStream)
  • @oldcurmudgeon-这将为我提供一个包含数据的通道,我如何获得在文件中写入数据的偏移量?我真的很困惑如果只有你可以分享一个代码 sn-p 使 fileChannel 工作谢谢:)
  • @user3804236 - 要写入的偏移量应在WriteBlockoffset 字段中。下载线程应该为写入线程填写。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-12
相关资源
最近更新 更多