首页 > 解决方案 > Java:使用 BufferedReader/InputStream 的多线程并发

问题描述

我有一个异步方法,每次调用 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 并发,但不确定解决此问题的最佳方法。

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

标签: javamultithreadingasynchronousconcurrency

解决方案


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

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

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

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

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


推荐阅读