Шаблон для ленивого потокобезопасного создания одноэлементов в 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");
    }
}

Спасибо

Ответы

Ответ 1

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

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

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

Выглядит слишком высоко.

Я действительно не вижу, как с помощником класс помогает.

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

Во-вторых, если вы ИМЕЕТЕ использовать singleton, почему бы не инициализировать экземпляр 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 на то, когда вам нужно инициализировать экземпляр singleton при первом получении. Во многих случаях я считаю, что этот подход также является излишним.

Ответ 5

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

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

Ответ 6

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