Шаблон для ленивых поточно-безопасных экземпляров синглтона в Java - PullRequest
16 голосов
/ 03 сентября 2010

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

Что вы думаете об этом?Вы видите что-то плохое в этом?Есть ли что-то похожее на 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");
    }
}

Спасибо

Ответы [ 6 ]

55 голосов
/ 03 сентября 2010

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

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

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%, что действительно требует отложенной загрузки при первойдоступ?Я так не думал.Тогда почему вы тратите свое время на придумывание этих мерзких кодов Rube Goldbergesque для решения несуществующей проблемы?

5 голосов
/ 03 сентября 2010

Для версии, которая более читабельна (на мой взгляд), чем представленная в вопросе, можно обратиться к идиоме Инициализация по требованию *1002*, предложенной Биллом Пью.Это не только поточно-ориентированный, учитывая модель памяти Java 5, синглтон также лениво инициализируется.

3 голосов
/ 03 сентября 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");
    }
}

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

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

3 голосов
/ 03 сентября 2010

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

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

0 голосов
/ 04 сентября 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 () не требуют синхронизации или произвольного чтения.первоначальная цель двойной проверки блокировки достигнута.

0 голосов
/ 03 сентября 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());

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

...