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

问题描述 投票:2回答:2

首先,我在编码方面还很陌生,所以我为我可能犯的任何误解表示歉意

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

该应用程序由许多面板和子面板组成,可从Web浏览器打开。单击子面板时,在前端侧执行三个不同的GET请求。

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

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

  • [此外,有时会发生以下情况:正确读取某些行后,突然bufferedReader.readLine()返回null,但是尚未完全读取文件。

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

我已经尝试过将[[synchronized做为parseFile方法(尽管我感觉不对,因为我不希望其他使用此方法的线程-读取其他文件-等待)。

下面包含代码段(逐行访问和读取文件)。

这是一个“精简”示例

。正如某些用户在下面评论的那样,此处不处理异常,而是在实际代码中处理are。这只是为了显示引起故障的部分。 // 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;
}

再次抱歉,我可能会犯任何错误,谢谢大家:)

编辑

  • 要多次读取的文件很小,但是

    无法存储在缓存中

,因为它与可能很快不会再次打开的面板有关。另外,有太多配置文件(每个子面板一个)无法缓存,每个文件都可能更改
  • 我强烈不希望不包括新库,因为我没有这样做的权限,我只需要通过在代码中进行小的更改来解决此问题
  • 此外,当

    debugging

  • 时,三个线程(每个线程打开自己的InputStream和一个BufferedReader用于同​​一配置文件)完美工作]
    答案

    [有效地,可以在不同的线程中创建InputStreams,它们都指向相同的文件,然后使用BufferedReader进行读取,而无需进行任何同步。

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

    我的代码中的错误是在getConfig方法中,该方法使用panel和subpanel参数构建fileName。在返回此fileName变量之前,此方法将文件从服务器下载(仅当文件已更改时)到target/目录中,以供本地访问。

    错误

    起作用的原因是该文件正在下载[[总是,因此在当前线程中读取该文件时,另一个线程正在重新下载(覆盖)该文件。在我本应放置在帖子中的代码下面查找: // 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 and return InputStream对我有用的原因。 
    这需要在不同位置修复问题:*仅当文件更改时(如预期的那样),我才需要下载文件*对于文件相同的情况(不使用download + InputStream creation字符串),我还将同步整个fileName
    java multithreading concurrency inputstream bufferedreader
    2个回答
    1
    投票
    请对以下代码进行必要的更改才能运行。

    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(); } }}


    0
    投票
    //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方法,该方法在文件相同时进行同步。
    请随时进行任何更正。我感谢他们
    © www.soinside.com 2019 - 2024. All rights reserved.