Фабрика объектов singleton: является ли этот код потокобезопасным?

У меня есть общий интерфейс для нескольких реализаций синглтонов. Интерфейс определяет метод инициализации, который может выбросить проверяемое исключение.

Мне нужна фабрика, которая будет возвращать кэшированные реализации 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.

6
задан anenvyguest 30 November 2011 в 19:24
поделиться