【发布时间】:2015-09-19 16:16:13
【问题描述】:
我是开发线程安全方法的新手。我有一个配置服务,实现为单例类,需要是线程安全的。当服务启动时,会读取一组配置文件并将其存储在映射中。这只需要发生一次。我已将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;
}
...
}
【问题讨论】:
-
那么,所有调用者都必须先调用
start(),然后才能调用任何其他方法?为什么不将初始化代码放在 getInstance() 方法中,以确保您获得的实例总是被初始化?另外,isStarted() 的意义何在,因为两个并行调用 start() 的线程都将读取文件并填充映射?您是否考虑过使用依赖注入框架来避免单反模式? -
@JB Nizet 他们可以,但不一定必须这样做。他们可以首先调用“isStarted()”,如果值为 false,则尝试启动服务。我不能确切地知道他们是否会做一个或另一个,所以我不得不假设他们可以做任何一个。不过,我喜欢您将所有这些都放在 getInstance() 方法中的想法。我想那会很安全。
-
只要是同步的,就可以。或者你可以使用单例持有者成语:en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
-
您愿意发布这个作为他问题的答案吗?
标签: java multithreading singleton concurrenthashmap