У меня есть общий интерфейс для нескольких реализаций синглтонов. Интерфейс определяет метод инициализации, который может выбросить проверяемое исключение.
Мне нужна фабрика, которая будет возвращать кэшированные реализации singleton по требованию, и интересно, является ли следующий подход потокобезопасным?
UPDATE1: Пожалуйста, не предлагайте никаких сторонних библиотек, так как это потребует получения юридического разрешения из-за возможных проблем с лицензированием :-)
UPDATE2: этот код, скорее всего, будет использоваться в среде EJB, поэтому желательно не порождать дополнительные потоки или использовать что-то подобное.
interface Singleton
{
void init() throws SingletonException;
}
public class SingletonFactory
{
private static ConcurrentMap<String, AtomicReference<? extends Singleton>> CACHE =
new ConcurrentHashMap<String, AtomicReference<? extends Singleton>>();
public static <T extends Singleton> T getSingletonInstance(Class<T> clazz)
throws SingletonException
{
String key = clazz.getName();
if (CACHE.containsKey(key))
{
return readEventually(key);
}
AtomicReference<T> ref = new AtomicReference<T>(null);
if (CACHE.putIfAbsent(key, ref) == null)
{
try
{
T instance = clazz.newInstance();
instance.init();
ref.set(instance); // ----- (1) -----
return instance;
}
catch (Exception e)
{
throw new SingletonException(e);
}
}
return readEventually(key);
}
@SuppressWarnings("unchecked")
private static <T extends Singleton> T readEventually(String key)
{
T instance = null;
AtomicReference<T> ref = (AtomicReference<T>) CACHE.get(key);
do
{
instance = ref.get(); // ----- (2) -----
}
while (instance == null);
return instance;
}
}
Я не совсем уверен насчет строк (1) и (2). Я знаю, что ссылающийся объект объявлен как поле volatile в AtomicReference
, и, следовательно, изменения, сделанные в строке (1), должны сразу же стать видимыми в строке (2) - но все еще есть некоторые сомнения...
В остальном - я думаю, что использование ConcurrentHashMap
решает проблему атомарности помещения нового ключа в кэш.
Видите ли вы, ребята, какие-либо проблемы с этим подходом? Спасибо!
P.S.: Я знаю об идиоме статического класса-держателя - и не использую ее из-за ExceptionInInitializerError
(в который заворачивается любое исключение, брошенное при инстанцировании синглтона) и последующего NoClassDefFoundError
, которые я не хочу ловить. Вместо этого я хотел бы использовать преимущество выделенного проверенного исключения, поймав его и обработав изящно, а не разбирать стек-трассировку EIIR или NCDFE.