对单例类方法的并发调用产生不一致的结果

问题描述

我有一个单例类,该类具有一个从目录读取所有文件的方法。传入configRootDirContentType(用于类型引用的Enum)。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,String filePattern){
        myOtherTypeMap.clear();
        readAllConfigsFromLocalDisk(configRootDir,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,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());
    }
}

一组Akka参与者同时调用了两个公共方法getMyTypeMapgetMyOtherTypeMap。在某些情况下将文件内容映射到对象时,我得到com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException

似乎原因是试图将configContent映射到MyType时实际上是MyOtherType可解析的,反之亦然。

我看了其他几个地方,但无法完整了解它。我试图了解同时调用readFile时发生的情况以及为什么混淆文件内容。有人可以帮助我了解这一点吗?预先感谢。

解决方法

您已经声明了两个 shared 变量:

private Map<String,MyType> myTypeMap = new HashMap();
private Map<String,MyOtherType> myOtherTypeMap = new HashMap();

由于HashMap不是线程安全的,所以当多个线程同时访问它的一个实例(并且至少有一个线程正在修改它)时,可能会发生最奇怪的事情。

使用线程安全映射无法解决语义问题,因为getMyTypeMap的所有调用都返回相同的映射实例并对其进行操作,因此调用者无法像其他仍在执行{{ 1}}(再次)对其进行更改。 getMyTypeMap的并发调用也是如此。

由于每种方法均以getMyOtherTypeMap调用开头,因此似乎不希望在该方法的不同调用之间共享数据,因此,这些方法不应共享数据。

看来,您遇到的主要障碍是如何重用代码以获取不同的结果类型。请勿使用该clear()类型:

enum

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...