我是开发线程安全方法的新手。我有一个配置服务,实现为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;
}
...
}
答案 0 :(得分:1)
您的代码实际上不是线程安全的,因为两个线程可能会同时调用start()并同时读取文件并填充映射。
使用它也很不愉快,因为你的单身人士的来电者将不断检查(或可能错误地假设)已经启动了单身人士,或者在调用任何其他方法之前调用start()以确保它开始了。
我会设计它,以便getInstance()
始终返回初始化的实例。确保getInstance()
已同步,以避免两个线程同时初始化实例。或者使用initialization on demand holder idiom。或者甚至更好,不要使用单例反模式,这使代码难以进行单元测试,而是使用依赖注入框架。