java - 对单例类方法的并发调用会产生不一致的结果
问题描述
我有一个单例类,它有一个从目录中读取所有文件的方法。configRootDir
传入and ContentType
(An Enum for type reference)方法,列出目录下的所有文件,readAllConfigsFromLocalDisk
并根据参数一一处理,将文件内容映射到期望的对象类型ContentType
。
// Config type reference
public enum ConfigType {
MY_TYPE, MY_OTHER_TYPE
}
// Singleton class
public class Singleton {
private static Singleton instance;
private Map<String, MyType> myTypeMap = new HashMap();
private Map<String, MyOtherType> myOtherTypeMap = new HashMap();
private Singleton() {}
public synchronized static Singleton getSingleton() {
if (istance == null)
istance = new Singleton();
return istance;
}
public Map<String,MyType> getMyTypeMap(String filePath, ConfigType configType, String filePattern){
myTypeMap.clear();
readAllConfigsFromLocalDisk(configRootDir, configType, filePattern);
return myTypeMap;
}
public Map<String,MyOtherType> getMyOtherTypeMap(String filePath, ConfigType configType, String filePattern){
myOtherTypeMap.clear();
readAllConfigsFromLocalDisk(configRootDir, configType, filePattern);
return myOtherTypeMap;
}
/**
* Get all files in config root directory and parse one by one
* @param configRootDir Root directory for configurations
* @param configType Configuration type
* @param filePattern File pattern
*/
private void readAllConfigsFromLocalDisk(String configRootDir, ConfigType configType, String filePattern) {
try (Stream<Path> walk = Files.walk(Paths.get(configRootDir))) {
Pattern pattern = Pattern.compile(filePattern);
List<Path> filePaths = getLocalFilePaths(walk, pattern);
if (!filePaths.isEmpty()) {
for (Path filePath : filePaths) {
String relativePath = filePath.toString();
parseConfigFile(relativePath, configType);
}
}
} catch (IOException ex) {
logger.error("Specified config root directory not found.", ex);
}
}
/**
* Read a given configuration file from local disk and map to specified config type
*
* @param configFile Relative path to config file on local disk
* @param configType Configuration type (MY_TYPE or MY_OTHER_TYPE)
*/
private void parseConfigFile(String filePath, ConfigType configType ){
String configContent = Files.readString(Paths.get(filePath), Charsets.UTF_8);
// Parse based on config type and overwrite map
switch (configType) {
case MY_TYPE:
MyTypeConf myTypeConf = Core.getMapper().readValue(configContent, MyTypeConf.class);
List<MyType> myTypeRefs = myTypeConf.getMyTypeList();
myTypeMap.putAll(myTypeRefs.stream().collect(Collectors.toMap(MyType::getId, Function.identity())));
case MY_OTHER_TYPE:
MyOtherTypeConf myOtherTypeConf = Core.getMapper().readValue(configContent, MyOtherTypeConf.class);
List<MyOtherType> myOtherTypeRefs = myOtherTypeConf.getMyOtherTypeList();
myOtherTypeMap.putAll(myOtherTypeRefs.stream().collect(Collectors.toMap(MyOtherType::getId, Function.identity())));
}
}
/**
* Get file paths of all matching files exist in configured streaming directory and sub folders from disk.
*
* @param walk Stream of paths in config root directory.
* @param pattern Pattern to math when discovering files.
* @return List of Path objects for all files matching the pattern.
*/
private List<Path> getLocalFilePaths(Stream<Path> walk, Pattern pattern) {
return walk.filter(Files::isRegularFile).filter(p -> {
String fileName = p.getFileName().toString();
Matcher matcher = pattern.matcher(fileName);
return matcher.matches();
}).collect(Collectors.toList());
}
}
两个公共方法getMyTypeMap
,getMyOtherTypeMap
由一组 Akka actor 同时调用。我com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
在某些情况下将文件内容映射到对象时得到。
似乎原因在尝试将其映射到时configContent
实际上是可解析的,反之亦然。MyType
MyOtherType
我看了其他几个地方,但无法得到它的全貌。我试图了解readFile
同时调用时会发生什么以及为什么它会混淆文件内容。有人可以帮助我理解这一点吗?提前致谢。
解决方案
您已经声明了两个共享变量:
private Map<String, MyType> myTypeMap = new HashMap();
private Map<String, MyOtherType> myOtherTypeMap = new HashMap();
由于HashMap
不是线程安全的,当多个线程同时访问它的一个实例(并且至少一个线程正在修改它)时,可能会发生最奇怪的事情。
使用线程安全映射不会解决语义问题,因为所有调用都getMyTypeMap
返回相同的映射实例并对其进行操作,因此调用者无法可靠地使用返回的映射,因为仍在执行的其他线程getMyTypeMap
正在(再次)更改它。这同样适用于 的并发调用getMyOtherTypeMap
。
由于每个方法都以clear()
调用开始,因此似乎不打算在方法的不同调用之间共享数据,因此,这些方法不应该共享数据。
看来,您的主要障碍是如何重用代码来获得不同的结果类型。不要使用那种enum
类型:
public class Singleton {
/**
* Classes are already lazily initialized, on first getSingleton() call
*/
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getSingleton() {
return instance;
}
public Map<String, MyType> getMyTypeMap(String configRootDir){
return readAllConfigsFromLocalDisk(configRootDir, "my-type-file-pattern",
MyTypeConf.class, MyTypeConf::getMyTypeList, MyType::getId);
}
public Map<String, MyOtherType> getMyOtherTypeMap(String configRootDir){
return readAllConfigsFromLocalDisk(configRootDir, "my-other-type-file-pattern",
MyOtherTypeConf.class,MyOtherTypeConf::getMyOtherTypeList,MyOtherType::getId);
}
/**
* Get all files in config root directory and parse one by one
* @param configRootDir Root directory for configurations
* @param filePattern File pattern
* @param confType Configuration type (MyTypeConf.class or MyOtherTypeConf.class)
* @param getList Configuration type specific list accessor method
* @param getId Result type specific Id accessor for the map key
*/
private <T,C> Map<String,T> readAllConfigsFromLocalDisk(
String configRootDir, String filePattern,
Class<C> confType, Function<C,List<T>> getList, Function<T,String> getId) {
try(Stream<Path> walk = Files.walk(Paths.get(configRootDir))) {
Pattern pattern = Pattern.compile(filePattern);
return getLocalFilePaths(walk, pattern)
.flatMap(p -> this.parseConfigFile(p, confType, getList))
.collect(Collectors.toMap(getId, Function.identity()));
} catch(IOException|UncheckedIOException ex) {
logger.error("Specified config root directory not found.", ex);
return Collections.emptyMap();
}
}
/**
* Read a given configuration file from local disk and map to specified config type
*
* @param configFile Path to config file on local disk
* @param configType Configuration type (MyTypeConf.class or MyOtherTypeConf.class)
* @param getList Configuration type specific list accessor method
*/
private <T,C> Stream<T> parseConfigFile(
Path configFile, Class<C> configType, Function<C,List<T>> getList) {
try {
C conf=Core.getMapper().readValue(Files.readString(configFile), configType);
List<T> tRefs = getList.apply(conf);
return tRefs.stream();
} catch(IOException ex) {
throw new UncheckedIOException(ex);
}
}
/**
* Get file paths of all matching files exist in configured streaming directory
* and sub folders from disk.
*
* @param walk Stream of paths in config root directory.
* @param pattern Pattern to math when discovering files.
* @return Stream of Path objects for all files matching the pattern.
*/
private Stream<Path> getLocalFilePaths(Stream<Path> walk, Pattern pattern) {
return walk.filter(Files::isRegularFile).filter(p -> {
String fileName = p.getFileName().toString();
Matcher matcher = pattern.matcher(fileName);
return matcher.matches();
});
}
}
推荐阅读
- woocommerce - WooCommerce 如何将完成的订单电子邮件发送到自定义字段电子邮件地址
- c# - 在 C# 中与 Xero 联系的 Web API
- excel - 在包含多行数据的 excel 中拆分单个单元格:
- python-2.7 - 使用 set_detailed_response 的问题 - ibm watson python sdk
- reactjs - React 代码在 localhost 上呈现正常,但是当我在 google chrome 中手动打开它时不起作用(通过直接打开 index.html 而不使用 npm)
- haproxy - 端口 80 的 Haproxy 后端服务器无法正常工作
- javascript - React:基于道具创建动态路由
- maven - 防止默认编译器在 Maven 中运行
- javascript - React owl carousel 是空白的
- javascript - 在键盘上按 Enter 时如何调用 JQUERY click() 方法?