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