Прежде всего, я довольно новичок в кодировании, поэтому я прошу прощения за любую ошибку argot, которую я, вероятно, совершаю
Я работаю над разработкой внутреннего сервера с использованием Java (openJDK11) и Spring boot :
Приложение состоит из множества панелей и подпанелей, которые открываются из веб-браузера. При щелчке на дополнительной панели три разных запроса 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
, используя параметры панели и подпанели. Этот метод перед возвратом этой переменной 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;
}
Когда я переместил создание InputStream в метод getInputStream
, я также включил туда загрузкуфайл. Вот почему синхронизация всего getInputStream = download file + create and return InputStream
сработала для меня.
Это требует исправления в разных местах: * Мне нужно скачивать файл только тогда, когда он изменился (как и ожидалось) * Я также синхронизировал бы весь download + InputStream creation
для случая, когда файл одинаков (не используется строка fileName
)