【问题标题】:Java: Multithreaded Concurrency with BufferedReader/InputStreamJava:使用 BufferedReader/InputStream 的多线程并发
【发布时间】:2021-09-22 00:50:48
【问题描述】:

我有一个异步方法,每次调用 api 端点时都会启动一个新线程来执行一些幕后处理。

我有一个缓冲读取器,它从进程中获取 inputStream 以获取特定行并将其写入磁盘上的文件。但是我认为我正在处理一些并发问题,因为有些文件是空的。这是我的示例代码:

    @Async
    public void runTaskAsync(String jobIDString) {
        Runtime runtime = Runtime.getRuntime();
        String line;
        String summary = "";

        try {
            Process gradlewProcess = runtime.exec( SOME PROCESS );
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gradlewProcess.getInputStream()));
            while ((line = bufferedReader.readLine()) != null) {
                if(line WE ARE LOOKING FOR) {
                    summary = line
                    break;
                }
            }

            //Write the summary to a txt file
            PrintWriter summaryWriter = new PrintWriter(FILE NAME);
            summaryWriter.println(summary);
            summaryWriter.close();

            //Create the tar file
            runtime.exec(TAR SOME FOLDER);

            runtime.exec(MOVE TAR FILE TO ANOTHER FOLDER);
        }
.
.
.

在某些情况下,TAR 文件不会移动到文件夹中(当多个线程同时运行时)。我假设这是由于 IO 并发造成的,但不确定解决此问题的最佳方法。

如何从进程输出中获取特定行并将其存储到诸如此类的多线程场景中的文件中?

【问题讨论】:

  • 文件将为空,因为输入流不包含任何“我们正在寻找的行”。明显地。 --- 仅供参考:您忘记关闭输入流。
  • 您正在线程中本地创建资源,因此您正在处理的线程问题极不可能。看起来您并没有等待 Process 对象完成,可能在应用程序第一次输出之前停止读取。
  • 你可以查看这个例子,它使用完整的未来在一个大文件上异步创建任务链。 thetechnojournals.com/2019/10/…

标签: java multithreading asynchronous concurrency


【解决方案1】:

如果FILE NAME 是不同线程的相同名称,显然这将无法正常工作并且无法正常工作。此外,你为什么在大火中使用所有东西的 exec 来mv 一个文件??

通常的策略是,至少在写入时(多个线程并发读取同一字段没什么大不了的,可能不是最有效的事情,但不是直接错误的直接来源),是写入一个唯一的命名文件(只需使用随机生成器卷起一个长的临时名称),然后依靠 ATOMIC_MOVE 原子地移动文件。这需要使用java.nio.file API,它允许您运行Files.move(from, to, StandardCopyOptions.ATOMIC_MOVE),如果目标已经存在,它将失败,并且只有在它不存在时才会成功,原子地(例如,如果很多线程都试图这样做同时使用相同的to,正好一个会成功,其他的都会失败,最终结果好像只有成功的那个跑了。

请注意,磁盘可能是瓶颈;试图通过打开 20 个线程来加速任务,每个线程都会打开一个大文件来搜索它以找到某些特定的行,这通常比只有一个线程执行此操作要慢近 20 倍。多线程肯定不是“更快”的巫术魔法。除非你非常小心,否则它通常会更麻烦而且更慢。

一旦您确定出于速度原因需要多核,然后对其进行精简。一个进程应该通读该文件,并开始创建自包含的作业“数据包”(通常是一个包含有关作业的所有相关信息的单个对象。在这种情况下,您正在寻找的那一行可能还有其他详细信息),然后这个系统将工作交给一个线程。该线程基于此工作包完成工作,并且依赖于其他任何东西。它不读取任何字段(或者至少没有与其他线程共享的字段),它根本不接触磁盘。它只是将计算结果返回给主组织线程,以确保正确保存结果。应该只有一个组织线程 - 通常,如果您尝试并行化它,只涉及磁盘的任务不会更快。

像 fork/join 这样的设计非常适合 - 或者在网上搜索“map reduce java”,我相信你会找到很多教程。请记住,在异步/多核世界中,FUD 绝对是雪崩式的,并且关于单线程“无法扩展”以及所有代码应该如何异步的谎言。如果这听起来有点说教,请跳过它,这将是那些不知道自己在做什么的疯子之一。不幸的是,将小麦与谷壳分开可能有点困难。

【讨论】:

  • 感谢您的回复!文件名实际上对于每个线程都是唯一的,我应该更清楚地说明这一点。我的后台进程实际上是一个长时间运行的 gradle 进程。我使它异步的原因是客户端不必坐下来等到过程完成。感谢 ATOMIC_MOVE 的建议
猜你喜欢
  • 2020-02-16
  • 2014-09-19
  • 2021-12-12
  • 2015-10-22
  • 1970-01-01
  • 1970-01-01
  • 2013-04-16
  • 1970-01-01
  • 2015-07-22
相关资源
最近更新 更多