Шаблон для ленивого потокобезопасного экземпляра синглтона в java

ленивый потокобезопасный одноэлементный экземпляр непросто понять каждому кодеру, поэтому я хотел создать класс в нашей корпоративной структуре, который бы выполнял эту работу.

Что вы думаете об этом? Вы видите в этом что-то плохое? Есть ли что-то подобное в Apache Commons? Как мне сделать это лучше?

Supplier.java

public interface Supplier<T> {
    public T get();
}

LazyThreadSafeInstantiator.java

public class LazyThreadSafeInstantiator<T> implements Supplier<T> {
    private final Supplier<T> instanceSupplier;

    private volatile T obj;

    public LazyThreadSafeInstantiator(Supplier<T> instanceSupplier) {
        this.instanceSupplier = instanceSupplier;
    }

    @Override
    // http://en.wikipedia.org/wiki/Double-checked_locking
    public T get() {
        T result = obj;  // Wikipedia: Note the usage of the local variable result which seems unnecessary. For some versions of the Java VM, it will make the code 25% faster and for others, it won't hurt.
        if (result == null) {
            synchronized(this) {
                result = obj;
                if (result == null) {
                    result = instanceSupplier.get();
                    obj = result;
                }
            }
        }
        return result;
    }
}

Пример использования:

public class Singleton1 {
    private static final Supplier<Singleton1> instanceHolder =
        new LazyThreadSafeInstantiator<Singleton1>(new Supplier<Singleton1>() {
            @Override
            public Singleton1 get() {
                return new Singleton1();
            }
        });

    public Singleton1 instance() {
        return instanceHolder.get();
    }

    private Singleton1() {
        System.out.println("Singleton1 instantiated");
    }
}

Спасибо


person Igor Mukhin    schedule 03.09.2010    source источник
comment
Моя самая большая проблема - это сам узор. Из Википедии: Некоторые считают это анти-шаблоном, полагая, что он чрезмерно используется, вводит ненужные ограничения в ситуациях, когда единственный экземпляр класса фактически не требуется, и вводит глобальное состояние в приложение. Вместо этого рассмотрите возможность использования внедрения зависимостей.   -  person Klaus Byskov Pedersen    schedule 03.09.2010
comment
Я настоятельно рекомендую избегать чего-то подобного. Вместо этого используйте структуру DI.   -  person matt b    schedule 03.09.2010
comment
возможный дубликат одноэлементного шаблона в java-отложенной инициализации   -  person Pascal Thivent    schedule 04.09.2010


Ответы (6)


ленивый потокобезопасный одноэлементный экземпляр непросто понять каждому кодеру

Нет, на самом деле это очень и очень просто:

public class Singleton{
    private final static Singleton instance = new Singleton();
    private Singleton(){ ... }
    public static Singleton getInstance(){ return instance; }
}

А еще лучше сделать это перечислением:

public enum Singleton{
    INSTANCE;
    private Singleton(){ ... }
}

Это потокобезопасный и ленивый (инициализация происходит во время загрузки класса, а Java не загружает классы до тех пор, пока на них не обратятся впервые).

Дело в том, что в 99% случаев вам вообще не нужна отложенная загрузка. А из оставшегося 1% в 0,9% вышеперечисленное совершенно лениво.

Вы запустили профилировщик и определили, что ваше приложение соответствует 0,01%, что действительно требует ленивой загрузки при первом доступе? Так не думала. Тогда зачем вы тратите время на сочинение этих мерзостей кода Руба Голдберге, чтобы решить несуществующую проблему?

person Michael Borgwardt    schedule 03.09.2010
comment
спасибо всем ребята за ответы. Думаю, я совершенно не понимал, чего хотел достичь. - person Igor Mukhin; 03.09.2010
comment
Все вы помогли мне решить настоящую проблему, которую я разместил по адресу stackoverflow.com/questions/3636244/ - person Igor Mukhin; 03.09.2010
comment
В class образце final static отсутствует. Я поправил. Я хочу добавить ссылку на этот пост из другого вопроса. - person Alexander Pogrebnyak; 08.09.2010
comment
@savinos: потому что это делает его автоматически потокобезопасным и сериализуемым при сохранении свойства singleton. Особенно в последнем случае потребуется много работы, чтобы исправить это вручную. - person Michael Borgwardt; 06.10.2011
comment
Может быть, это глупый вопрос, но что добавляется, если конструктор выдает исключение? - person Pith; 24.07.2013
comment
@interlude: да, я уверен. - person Michael Borgwardt; 04.12.2013
comment
Что произойдет в случае одновременного вызова getInstance? Говоря, что это потокобезопасный, мы также говорим, что статический конструктор является атомарным. - person interlude; 04.12.2013
comment
@interlude: спецификация JVM гарантирует, что инициализация класса (где и произойдет вызов конструктора) будет синхронизирована. Вот подробности: docs.oracle. com / javase / specs / jvms / se7 / html / jvms-5.html # jvms-5.5. - person Michael Borgwardt; 04.12.2013
comment
Вы предполагаете, что загрузчик классов не является пользовательским загрузчиком классов. - person interlude; 04.12.2013
comment
@interlude: Можете ли вы указать, где гарантии, предоставляемые спецификацией, ограничиваются загрузчиком системных классов? - person Michael Borgwardt; 04.12.2013
comment
позвольте нам продолжить это обсуждение в чате - person interlude; 04.12.2013
comment
Моя проблема в том, что у меня есть код инициализации, который выполняет ввод-вывод и поэтому может генерировать исключения. Я не хочу выполнять его во время строительства. Я хочу, чтобы с конструктором покончено. Затем мне нужен отдельный метод инициализации, который выполняет вызовы ввода-вывода. - person Mike Beckerle; 06.01.2014

Для версии, которая более читабельна (на мой взгляд), чем та, которая представлена ​​в вопросе, можно сослаться на Идиома "Инициализация по требованию", представленная Биллом Пью. С учетом модели памяти Java 5 это не только потокобезопасно, синглтон также инициализируется лениво.

person Vineet Reynolds    schedule 03.09.2010

На мой взгляд, это слишком сложно.

Я действительно не понимаю, как помогает наличие вспомогательного класса.

Во-первых, он использует идиому двойной блокировки, и это неоднократно доказывалось, что она сломана.

Во-вторых, если вы ДОЛЖНЫ использовать синглтон, почему бы не инициализировать static final экземпляр.

public class Singleton1 {
    private static final Singleton1 instanceHolder =
        new Singletong1( );

    public Singleton1 instance() {
        return instanceHolder;
    }

    private Singleton1() {
        System.out.println("Singleton1 instantiated");
    }
}

Этот код является потокобезопасным и доказал свою работоспособность.

Проверьте ответ Винита Рейнольдса, когда вам нужно инициализировать синглтон-экземпляр при первом получении. Во многих случаях я считаю, что такой подход тоже излишний.

person Alexander Pogrebnyak    schedule 03.09.2010

Разве это не дважды проверенный шаблон блокировки и использование изменчивого сломан на JIT-компиляторах и многоядерных / процессорных системах из-за модели памяти Java и возможности выполнения вне очереди?

В общем, кажется, что фреймворк для синглтонов - излишний, потому что, по сути, довольно простой шаблон для правильной реализации.

person Joel    schedule 03.09.2010
comment
У меня создалось впечатление, что они исправили в java 1.5 - person mR_fr0g; 03.09.2010
comment
@ mR_fr0g, нет. Отчасти проблема с этим анти-шаблоном состоит в том, что он пытается прочитать ссылку без блокировки. Для правильной синхронизации требуется блокировка всех чтений и записей, чтобы потоки гарантированно видели обновления из других потоков. - person matt b; 03.09.2010
comment
У вас довольно старая информация. Java 1.5 вышла в конце 2004 года, и с тех пор это исправлено (пока поле помечено volatile). Фактически, Java 1.5 настолько устарела, что Sun прекратила ее поддержку в ноябре прошлого года. - person Daniel Martin; 03.09.2010
comment
@matt b - Нет, это работает и совершенно безопасно с volatile ref. См. cs.umd.edu/~pugh /java/memoryModel/jsr-133-faq.html#dcl - person Daniel Martin; 03.09.2010

Я бы согласился с другими постерами и сказал, что это действительно кажется излишним, но сказал, что я действительно думаю, что это то, что младший разработчик, вероятно, ошибется. Я думаю, что, поскольку поведение поставщика, который создает синглтон (показанный ниже), будет одинаковым почти во всех случаях, у меня возникнет соблазн указать это как поведение по умолчанию в LazyThreadSafeInstantiator. Использование анонимного внутреннего класса каждый раз, когда вы хотите использовать синглтон, действительно беспорядочно.

        @Override
        public Singleton1 get() {
            return new Singleton1();
        }

Это можно сделать, предоставив перегруженный конструктор, который переводит класс в требуемый синглтон.

public class LazyThreadSafeInstantiator<T> implements Supplier<T> {
    private final Supplier<T> instanceSupplier;

    private Class<T> toConstruct;

    private volatile T obj;

    public LazyThreadSafeInstantiator(Supplier<T> instanceSupplier) {
        this.instanceSupplier = instanceSupplier;
    }

    public LazyThreadSafeInstantiator(Class<t> toConstruct) {
        this.toConstruct = toConstruct;
    }

    @Override
    // http://en.wikipedia.org/wiki/Double-checked_locking
    public T get() {
        T result = obj;  // Wikipedia: Note the usage of the local variable result which seems unnecessary. For some versions of the Java VM, it will make the code 25% faster and for others, it won't hurt.
        if (result == null) {
            synchronized(this) {
                result = obj;
                if (result == null) {
                    if (instanceSupplier == null) {
                      try {
                        Constructor[] c = toConstruct.getDeclaredConstructors();
                        c[0].setAccessible(true);
                        result = c[0].newInstance(new Object[] {});
                      } catch (Exception e) {
                        //handle
                      }
                      result = 
                    } else {
                      result = instanceSupplier.get();
                    }
                    obj = result;
                }
            }
        }
        return result;
    }
}

Затем это будет использоваться так.

private static final Supplier<Singleton1> instanceHolder =
    new LazyThreadSafeInstantiator<Singleton1>(Singleton1.getClass());

Это мое мнение немного чище. Вы также можете расширить это, чтобы использовать аргументы конструктора.

person mR_fr0g    schedule 03.09.2010
comment
Хорошая идея, но вы должны сделать конструктор общедоступным, чтобы он запустился. - person Igor Mukhin; 03.09.2010
comment
Да, хороший момент. Вы можете использовать отражение для доступа к конструктору и сделать его доступным. Это было бы немного взломано, поэтому, если вы это сделаете, убедитесь, что вы задокументировали это до чертиков. - person mR_fr0g; 03.09.2010

Lazy<X> lazyX= new Lazy<X>(){
    protected X create(){
        return new X();
    }};

X x = lazyX.get();

abstract public class Lazy<T>
{
    abstract protected T create();

    static class FinalRef<S>
    {
        final S value;
        FinalRef(S value){ this.value =value; }
    }

    FinalRef<T> ref = null;

    public T get()
    {
        FinalRef<T> result = ref;
        if(result==null)
        {
            synchronized(this)
            {
                if(ref==null)
                    ref = new FinalRef<T>( create() );
                result = ref;
            }
        }
        return result.value;
    }
}

За исключением, может быть, первого get () в потоке, все вызовы get () не требуют синхронизации или непостоянного чтения. первоначальная цель блокировки с двойной проверкой достигнута.

person irreputable    schedule 04.09.2010
comment
перебор как по мне. не самый простой способ. - person ses; 20.02.2013