Я новичок в разработке потокобезопасных методов. У меня есть служба конфигурации, реализованная как класс 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
перед тем заселению карты, или даже если это вообще. Будет ли эта реализация достаточно надежной в многопоточной среде?
UPDATE:
Используя предложение 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() параллельно, будут читать файлы и заполнять карту в любом случае? Рассматривали ли вы использование рамки инъекций зависимостей, которая позволила бы избежать использования анти-шаблона singleon? –
@JB Nizet Они могли, но они не обязательно должны. Они могут сначала вызвать «isStarted()», и если значение ложно, попробуйте запустить службу. Я не могу окончательно узнать, будут ли они делать то или другое, поэтому я должен предположить, что они могут сделать одно. Мне нравится ваша идея поместить все это в метод getInstance(). Думаю, это было бы довольно безопасно. – Selena
Пока он синхронизирован, да. Или вы можете использовать икону владельца синглтона: https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom –