【发布时间】:2020-02-04 03:37:35
【问题描述】:
首先,我是编码新手,所以对于我可能犯的任何错误,我深表歉意
我正在使用 Java (openJDK11) 和 Spring boot 开发后端服务器:
该应用程序由许多面板和子面板组成,这些面板和子面板可以从 Web 浏览器中打开。单击子面板时,前端会执行三个不同的 GET 请求。
这三个请求期望不同的响应(json 架构、json 数据等)。每个请求都会启动一个访问同一个配置文件(每个子面板有一个配置文件)的不同线程,该线程会被解析。读取配置文件后,每个线程执行不同的操作,它们的共同点只有 config-reader 部分。
有时(这让我想到了并发),其中一个/一些线程中的读取操作无法执行,因为
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。此方法在返回此 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 对我有用。
这需要在不同的地方修复东西:
* 我只需要在文件发生变化时才下载文件(如预期的那样)
* 如果文件相同(不使用fileName 字符串),我也会同步整个download + InputStream creation
【问题讨论】:
-
如果你正在读取的文件很小,你经常从同一个文件中读取,并且你很少更新文件,考虑缓存每个文件中的
PanelSchemaEntity。例如,Guava 有一些缓存实用程序,并且应该与并发访问一起使用。只要确保每次更新文件时都使缓存无效。 Guava 的 CacheBuilder,如果你不想自己做缓存:guava.dev/releases/snapshot-jre/api/docs/com/google/common/… -
谢谢@simonsays,我认为这是个好主意,但恐怕我不允许使用 Guava 库或任何新库,除非非常必要。实际上,文件很小并且很少更新,但我无法将其存储在缓存中。
-
如果出现异常,您的代码会泄漏资源。使用 try-with-resources。
-
嗨,请分享xml文件
-
这是真正的代码吗?您确定
FileInputStream和BufferedReader是局部变量吗?不是实例变量或静态变量?
标签: java multithreading concurrency inputstream bufferedreader