线程安全的单件服务类,一次性初始化Map字段

时间:2015-09-19 16:16:14

标签: java multithreading singleton concurrenthashmap

我是开发线程安全方法的新手。我有一个配置服务,实现为Singleton类,需要是线程安全的。启动服务时,将读取一组配置文件并将其存储在映射中。这只需要发生一次。我已将AtomicBoolean用于isStarted州字段,但我不确定我是否已正确完成此操作:

public class ConfigServiceImpl implements ConfigService {
    public static final URL PROFILE_DIR_URL =
           ConfigServiceImpl.class.getClassLoader().getResource("./pageobject_config/");

    private AtomicBoolean isStarted;
    private Map<String,ConcurrentHashMap<String,LoadableConfig>> profiles = new ConcurrentHashMap<>();

    private static final class Loader {
        private static final ConfigServiceImpl INSTANCE = new ConfigServiceImpl();
    }

    private ConfigServiceImpl() { }

    public static ConfigServiceImpl getInstance() {
        return Loader.INSTANCE;
    }

    @Override
    public void start() {
        if(!isStarted()) {
            try {
                if (PROFILE_DIR_URL != null) {
                    URI resourceDirUri = PROFILE_DIR_URL.toURI();
                    File resourceDir = new File(resourceDirUri);
                    @SuppressWarnings("ConstantConditions")
                    List<File> files = resourceDir.listFiles() != null ?
                            Arrays.asList(resourceDir.listFiles()) : new ArrayList<>();

                    files.forEach(this::addProfile);
                    isStarted.compareAndSet(false, true);
                }
            } catch (URISyntaxException e) {
                throw new IllegalStateException("Could not generate a valid URI for " + PROFILE_DIR_URL);
            }
        }
    }

    @Override
    public boolean isStarted() {
        return isStarted.get();
    }

    ....
}

我不确定在填充地图之前是否应将isStarted设置为true,或者即使这很重要。这种实现在多线程环境中是否合理安全?

更新:

使用zapl的建议在私有构造函数中执行所有初始化,并使用JB Nizet建议使用getResourceAsStream()

public class ConfigServiceImpl implements ConfigService {
    private static final InputStream PROFILE_DIR_STREAM =
            ConfigServiceImpl.class.getClassLoader().getResourceAsStream("./pageobject_config/");

    private Map<String,HashMap<String,LoadableConfig>> profiles = new HashMap<>();

    private static final class Loader {
        private static final ConfigServiceImpl INSTANCE = new ConfigServiceImpl();
    }

    private ConfigServiceImpl() {
        if(PROFILE_DIR_STREAM != null) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(PROFILE_DIR_STREAM));
            String line;

            try {
                while ((line = reader.readLine()) != null) {
                    File file = new File(line);
                    ObjectMapper mapper = new ObjectMapper().registerModule(new Jdk8Module());
                    MapType mapType = mapper.getTypeFactory()
                            .constructMapType(HashMap.class, String.class, LoadableConfigImpl.class);

                    try {
                        //noinspection ConstantConditions
                        profiles.put(file.getName(), mapper.readValue(file, mapType));
                    } catch (IOException e) {
                        throw new IllegalStateException("Could not read and process profile " + file);
                    }

                }

                reader.close();
            } catch(IOException e) {
                throw new IllegalStateException("Could not read file list from profile directory");
            }
        }
    }

    public static ConfigServiceImpl getInstance() {
        return Loader.INSTANCE;
    }

    ...
}

1 个答案:

答案 0 :(得分:1)

您的代码实际上不是线程安全的,因为两个线程可能会同时调用start()并同时读取文件并填充映射。

使用它也很不愉快,因为你的单身人士的来电者将不断检查(或可能错误地假设)已经启动了单身人士,或者在调用任何其他方法之前调用start()以确保它开始了。

我会设计它,以便getInstance()始终返回初始化的实例。确保getInstance()已同步,以避免两个线程同时初始化实例。或者使用initialization on demand holder idiom。或者甚至更好,不要使用单例反模式,这使代码难以进行单元测试,而是使用依赖注入框架。