对单例 class 方法的并发调用产生不一致的结果
Concurrent calls to singleton class method produces inconsistent results
我有一个单例 class,它有一个从目录中读取所有文件的方法。传入configRootDir
和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());
}
}
两个 public 方法 getMyTypeMap
和 getMyOtherTypeMap
被一组 Akka actor 同时调用。在某些情况下,将文件内容映射到对象时,我得到 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
。
似乎原因是 configContent
在尝试将其映射到 MyOtherType
时实际上 MyType
可解析,反之亦然。
我看了其他几个地方,但无法全面了解它。我试图了解同时调用 readFile
时会发生什么,以及为什么它会混淆文件内容。有人可以帮助我理解这一点吗?提前致谢。
您声明了两个共享变量:
private Map<String, MyType> myTypeMap = new HashMap();
private Map<String, MyOtherType> myOtherTypeMap = new HashMap();
由于 HashMap
不是 thread-safe,最奇怪的事情可能发生在多个线程同时访问它的一个实例(并且至少有一个线程正在修改它)时。
使用线程安全映射不会解决语义问题,因为 getMyTypeMap
return 的所有调用都是同一个映射实例并对其进行操作,因此调用者不能使用 returned 映射可靠,因为其他线程仍在执行 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();
});
}
}
我有一个单例 class,它有一个从目录中读取所有文件的方法。传入configRootDir
和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());
}
}
两个 public 方法 getMyTypeMap
和 getMyOtherTypeMap
被一组 Akka actor 同时调用。在某些情况下,将文件内容映射到对象时,我得到 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
。
似乎原因是 configContent
在尝试将其映射到 MyOtherType
时实际上 MyType
可解析,反之亦然。
我看了其他几个地方,但无法全面了解它。我试图了解同时调用 readFile
时会发生什么,以及为什么它会混淆文件内容。有人可以帮助我理解这一点吗?提前致谢。
您声明了两个共享变量:
private Map<String, MyType> myTypeMap = new HashMap();
private Map<String, MyOtherType> myOtherTypeMap = new HashMap();
由于 HashMap
不是 thread-safe,最奇怪的事情可能发生在多个线程同时访问它的一个实例(并且至少有一个线程正在修改它)时。
使用线程安全映射不会解决语义问题,因为 getMyTypeMap
return 的所有调用都是同一个映射实例并对其进行操作,因此调用者不能使用 returned 映射可靠,因为其他线程仍在执行 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();
});
}
}