当前位置: 首页 > 知识库问答 >
问题:

Java:从不同的线程读取相同的文件有时会在某些线程中返回空内容

丁嘉
2023-03-14

首先,我在编码方面是个新手,所以我为我可能犯的任何隐语错误道歉

我正在使用Java(openJDK11)和Spring boot开发后端服务器:

该应用程序由许多面板和子面板组成,这些面板和子面板从web浏览器打开。单击子面板时,在html" target="_blank">前端执行三个不同的GET请求。

这三个请求需要不同的响应(json模式、json数据等)。每个请求启动一个访问相同配置文件的不同线程(每个子面板有一个配置文件),该配置文件将被解析。读取配置文件后,每个线程执行不同的操作,它们的共同点只有配置读取器部分。

>

  • 有时(这让我想到并发性),其中一个/一些线程中的读取操作无法执行,因为bufferedReader。readLine()返回null,不读取任何行。

    此外,有时在正确读取某些行之后,突然bufferedReader。readLine()返回null,但文件尚未完全读取。

    每个线程创建一个本地InputStream来打开文件,并创建一个本地BufferedReader来解析文件。

    我已经尝试使同步的parseFile方法(虽然我觉得这是不对的,因为我不希望其他线程使用这个方法-读取其他文件-等待)。

    以下是被访问和读取文件的代码片段(逐行)。这是一个“缩小”的例子。正如一些用户在下面评论的那样,异常不在这里处理,而是在真正的代码中。这只是为了显示引起麻烦的部分。

    
    // REST CONTROLLER
    
    @GetMapping(value = "/schema/{panel}/{subpanel}")
    public PanelSchemaEntity getSchema(String panel, String subpanel)
    {
      //Retrieves the config-file name associated to the given panel+subpanel
      String fileName = getConfig(panel, subpanel);
      // fileName = "target/config/panelABC1.txt"
    
      InputStream input = new FileInputStream(fileName);
      PanelSchemaEntity schema = new PanelSchemaEntity();
      parseFile(schema, input);
    
      return schema;
    }
    
    @GetMapping(value = "/data/{panel}/{subpanel}")
    public PanelDataEntity get(String panel, String subpanel)
    {
      //Retrieves the config-file name associated to the given panel+subpanel
      String fileName = getConfig(panel, subpanel);
      // fileName = "target/config/panelABC1.txt"
    
      InputStream input = new FileInputStream(fileName);
      PanelSchemaEntity schema = new PanelSchemaEntity();
      parseFile(schema, input);
    
      String dataFileName = getDataFile(panel, subpanel);
      // dataFileName = "target/config/panelABC1.dat"
      InputStream data = new FileInputStream(dataFileName);
    
      return new PanelDataEntity(schema, data);
    }
    
    
    // PLACED IN SOME UTILS PACKAGE
    
    // Fills the PanelSchemaEntity with the content read from input
    public PanelSchemaEntity parseFile(PanelSchemaEntity schema, InputStream input)
    {
      BufferedReader reader = new BufferedReader(new InputStreamReader(input));
      String nextLine = reader.readLine();
    
      // The data file is read and used to complete panel schema entity
      while(nextLine != null)
      {
        // Here goes the code that uses each line's content to 
        // fill some schema's attributes
      }
      reader.close();
      return schema;
    }
    
    

    再次为我可能犯的任何错误感到抱歉,谢谢大家:)

    >

  • 要多次读取的文件很小,但无法存储在缓存中,因为它与可能很快无法再次打开的面板相关。此外,要缓存的配置文件太多(每个子面板一个),每个文件都可能会更改

    我强烈不希望包含新的库,因为我没有这样做的权限,我只需要通过在代码中包含一些小的更改来修复这种行为

    此外,在调试时,3个线程(每个线程为同一配置文件打开自己的InputStream和一个BufferedReader)工作正常

    实际上,可以在不同的线程中创建InputStreams,所有线程都指向同一个文件,然后使用BufferedReader读取,而无需同步任何内容。

    我的第一个错误是在这里显示原始代码的过于简化的版本。我专注于展示我所认为的问题所在。这是我的第一篇文章,下次我会做得更好。

    我的代码中的错误出现在getConfig方法中,该方法使用面板和子面板参数构建fileName。此方法在返回此文件名变量之前,将文件从服务器(仅当其已更改)下载到本地访问的目标/目录。问题是文件总是被下载,所以在当前线程中读取时,它被另一个线程重新下载(覆盖)。

    下面是我原本应该放在帖子中的代码:

    
    // REST CONTROLLER
    
    @GetMapping(value = "/schema/{panel}/{subpanel}")
    public PanelSchemaEntity getSchema(String panel, String subpanel)
    {
      //Retrieves the config-file name associated to the given panel+subpanel
      ConfigFile configFile = getConfig(panel, subpanel);
      // configFile.getPath() = "target/config/panelABC1.txt"
    
      InputStream input = new FileInputStream(configFile.getPath());
      PanelSchemaEntity schema = new PanelSchemaEntity();
      parseFile(schema, input);
    
      return schema;
    }
    
    @GetMapping(value = "/data/{panel}/{subpanel}")
    public PanelDataEntity get(String panel, String subpanel)
    {
      //Retrieves the config-file name associated to the given panel+subpanel
      ConfigFile configFile = getConfig(panel, subpanel);
      // configFile.getPath() = "target/config/panelABC1.txt"
    
      InputStream input = new FileInputStream(configFile.getPath());
      PanelSchemaEntity schema = new PanelSchemaEntity();
      parseFile(schema, input);
    
      String dataFileName = getDataFile(panel, subpanel);
      // dataFileName = "target/config/panelABC1.dat"
      InputStream data = new FileInputStream(dataFileName);
    
      return new PanelDataEntity(schema, data);
    }
    
    
    // PLACED IN SOME UTILS PACKAGE
    
    // Creates fileName and downloads file (if changed)
    public ConfigFile getConfig(String panel, String subpanel)
    {
      String filePathInServer = findFilePathInServer(panel, subpanel);
    
      // ERROR here: the download was happening always
      String localFilePath = donwloadIfChanged(filePathInServer); 
    
      ConfigFile configFile = new ConfigFile(localFilePath);
    
      return configFile;
    }
    
    
    // PLACED IN SOME UTILS PACKAGE
    
    // Fills the PanelSchemaEntity with the content read from input
    public PanelSchemaEntity parseFile(PanelSchemaEntity schema, InputStream input)
    {
      BufferedReader reader = new BufferedReader(new InputStreamReader(input));
      String nextLine = reader.readLine();
    
      // The data file is read and used to complete panel schema entity
      while(nextLine != null)
      {
        // Here goes the code that uses each line's content to 
        // fill some schema's attributes
      }
      reader.close();
      return schema;
    }
    
    

    当我在getInputStream方法中移动InputStream创建时,我还包括了文件的下载。这就是为什么同步整个getInputStream=download file create和return InputStream对我有效的原因。

    这需要在不同的地方修复问题:*我只需要在文件发生更改时下载文件(如预期的那样)*对于文件相同的情况(不使用文件名字符串),我还将同步整个下载输入流创建

  • 共有2个答案

    陆俊智
    2023-03-14

    对我的案子起作用的是以下几点:

    //REST CONTROLLER
    @GetMapping(value = "/schema/{panel}/{subpanel}")
    public PanelSchemaEntity getSchema(String panel, String subpanel)
    {
      //Retrieves the config-file name associated to the given panel+subpanel
      String fileName = getConfig(panel, subpanel);
      // fileName = "target/config/panelABC1.txt"
    
      InputStream input = getInputStream(fileName);
      PanelSchemaEntity schema = new PanelSchemaEntity();
      return parseFile(schema, input);
    }
    
    @GetMapping(value = "/data/{panel}/{subpanel}")
    public PanelDataEntity get(String panel, String subpanel)
    {
      //Retrieves the config-file name associated to the given panel+subpanel
      String fileName = getConfig(panel, subpanel);
      // fileName = "target/config/panelABC1.txt"
    
      InputStream input = getInputStream(fileName);
      PanelSchemaEntity schema = new PanelSchemaEntity();
      parseFile(schema, input);
    
      String dataFileName = getDataFile(panel, subpanel);
      // dataFileName = "target/config/panelABC1.dat"
      InputStream data = new FileInputStream(dataFileName);
      return new PanelDataEntity(schema, data);
    }
    
    // PLACED IN SOME UTILS PACKAGE
    
    public InputStream getInputStream(String file)
    {
      synchronize (file)
      {
        InputStream input = new FileInputStream(fileName);
      }
    }
    
    // Fills the PanelSchemaEntity with the content read from input
    public PanelSchemaEntity parseFile(PanelSchemaEntity schema, InputStream input)
    {
      BufferedReader reader = new BufferedReader(new InputStreamReader(input));
      String nextLine = reader.readLine();
    
      // The data file is read and used to complete panel schema entity
      while(nextLine != null)
      {
        // Here goes the code that uses each line's content to 
        // fill some schema's attributes
      }
      reader.close();
      return schema;
    }
    

    将InputStream初始化移动到getInputStream方法,该方法在文件相同时进行同步。

    请随时更正。我很感激他们

    任文乐
    2023-03-14

    您的问题与并发无关。这里是同时读取文件的示例。并发可能发生在同时写入和读取文件的情况下。但是对于读取文件来说,不存在数据不一致或并发的问题。

    请对以下代码进行必要的更改以运行。

    public static void main(String[] args) {
        String fileName = "/home/note.xml";
        FileReadThread frth1 = new FileReadThread(fileName, "ThreadOne");
        FileReadThread frth2 = new FileReadThread(fileName, "ThreadTwo");
        FileReadThread frth3 = new FileReadThread(fileName, "ThreadThree");
        frth1.start();
        frth2.start();
        frth3.start();
    }}
    
    
    
    private String fileName;
    private String threadName;
    public FileReadThread(String fileName, String threadName) {
        this.fileName = fileName;
        this.threadName = threadName;
    }
    
    @Override
    public void run() {
        InputStream input;
        try {
            input = new FileInputStream(fileName);
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
            String strCurrentLine;
            while ((strCurrentLine = reader.readLine()) != null) {
                System.out.println(threadName + "--" + strCurrentLine);
            }
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }}
    
     类似资料:
    • 我的问题与这个老问题很相似,但没有令人满意的答案贴在那里。 在DB2中有一个DB表,我试图通过两个或多个单独的Java线程对其进行并行记录读取,前提是这些线程应该读取不同的数据集,即如果线程1读取了前1000条记录,线程2不应该选择这些记录,而是选择不同的1000条记录(如果可用的话)。 由于线程将读取不同的行,因此在读取时不会发生冲突。Connection对象也不会在线程之间共享--它们将使用自

    • 我的程序如下: 输入: 提交ID名称薪资部门编号 提交ID1名称1 25100部门1 提交ID2名称2 25200部门2 提交ID3名称3 25300部门3 提交ID4名称4 25400部门4 提交ID5名称5 25500部门5 提交ID6名称6 25600部门6 提交ID7名称7 25700部门7 提交ID8名称8 25800部门8 提交ID9名称9 25900部门9 提交ID10名称10 26

    • 我最近将应用程序从log4j更改为logback/slf4j。一切都工作得很好,但我想实现一些具体的东西。 我正在开发的应用程序是一个web应用程序。在我们的生产环境中,日志级别是on info。不时有票进来让我们的服务团队处理。如果我们的服务团队在复制票据时,他们可以将日志级别放在跟踪上,只用于他们的测试请求,那就太好了。这样,日志文件就不会随着当时所有其他请求的到来而被修改。 我们已经使用标头

    • 问题内容: 据我所知,操作系统创建线程时,每个线程都会获得一个不同的堆栈。我想知道每个线程是否也有与自己不同的堆吗? 问题答案: 否。所有线程共享一个公共堆。 每个线程都有一个专用堆栈,它可以快速添加和删除其中的项目。这样可以使基于堆栈的内存速度更快,但是,如果您使用过多的堆栈内存(如无限递归中所发生的那样),则会导致堆栈溢出。 由于所有线程共享同一个堆,因此必须同步对分配器/释放器的访问。有许多

    • 问题内容: 我有一个方法。值在内部被更改,我想将其返回给该方法。有没有办法做到这一点? 问题答案: 可以使用局部最终变量数组。该变量必须是非基本类型,因此可以使用数组。你还需要同步两个线程,例如使用CountDownLatch: 你也可以这样使用an Executor和a Callable:

    • 问题内容: 我有一个如下的Java线程: 我大约有300个ID,每隔几秒钟-我启动线程以对每个ID进行呼叫。例如。 现在,我想从每个线程收集结果,并批量插入数据库,而不是每2秒进行300次数据库插入。 知道我该如何做到吗? 问题答案: 如果要在执行数据库更新之前收集所有结果,则可以使用该方法。如daveb建议的那样,如果您一次提交一项任务,则可以完成簿记工作。