首先,我在编码方面还很陌生,所以我为我可能犯的任何误解表示歉意
我正在使用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
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();
}
}}
//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
方法,该方法在文件相同时进行同步。请随时进行任何更正。我感谢他们