Подтвердить что ты не робот

Как написать Singleton надлежащим образом?

Сегодня в моем интервью один интервьюер попросил меня написать класс Singleton. И я дал свой ответ как

public class Singleton {

    private static Singleton ref;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (ref == null) {
            ref = new Singleton();
        }
        return ref;
    }
}

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

4b9b3361

Ответ 1

Первое, что приходит мне в голову при создании синглета, - это enum. Обычно я использую enum для реализации singleton:

enum Singleton {
    INSTANCE;
}

Одно из преимуществ, которое вы получаете при использовании перечисления, - это сериализация.

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

Используя класс, вы должны создать синглтон следующим образом:

public final class Singleton implements Serializable {
    // For lazy-laoding (if only you want)
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
        if (SingletonHolder.INSTANCE != null) {
            // throw Some Exception
        }
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // To avoid deserialization create new instance
    @SuppressWarnings("unused")
    private Singleton readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

Ответ 2

Последние стандартные решения:

  • Ядро Java с управляемым Beans/CDI

    @ApplicationScoped
    public class MySingleton { ... }
    
  • EJB Lite (JEE6)

    @Singleton
    public class MySingleton { ... }
    

Предварительная рекомендация (от "Эффективная Java 2" ):

  • Используйте перечисление с одной ложной константой перечисления, например. ПРИМЕР. Поля и методы данных могут быть как статическими, так и нестационарными (экземпляр) - эти два ведут себя одинаково, поскольку существует только один экземпляр

  • Преимущества:

    • Да, это синглтон. Невозможно создать несколько экземпляров любым механизмом (в любом заданном загрузчике классов на любом JVM).
    • Инициализация синглтона является потокобезопасной.
  • Недостатки (по сравнению с выше стандартными решениями):

    • Определение немного тупое - внешняя оболочка "enum" может неправильно направлять неопытных разработчиков кода. Это небольшой взлом, а не первоначальное намерение перечисления.
    • Реализация протекает через абстракцию. Он нарушает Единый принцип доступа
      • Он не работает через инъекцию - клиент должен знать, что это синглтон и код против точного экземпляра enum
      • Клиент должен использовать Singleton.INSTANCE.someMethod()
      • Клиент не может (просто) кодировать интерфейс
      • Клиент, на который влияют изменения дизайна между одноточечными объектами в/из объектов с несколькими экземплярами
    • Расширяет класс предков (Enum), предотвращая наследование от других классов
    • Сериализация и десериализация просто передают имя переменной enum без состояния - хорошо для технического обеспечения там только одного экземпляра, но нулевой операции в отношении данных. В (редком) событии хотелось разделить/синхронизировать одноэлементное состояние через загрузчики JVM/class, репликация через сериализацию не может быть использована.

Ответ 3

Вы можете сделать

public enum Singleton {
    INSTANCE;
}

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

public enum Utility {
     ;

     public static void method();
}

Ответ 4

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

У нас был Синглтон в виде:

public enum Foo {
    INSTANCE;
}

который был вокруг на некоторое время, работает отлично. Затем во время просмотра кода мы увидели следующее:

public enum Foo {
    INSTANCE,
    ANOTHER;
}

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

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

EDIT:

Для полноты здесь перечисление Singleton, которое защищает дополнительные значения, добавляемые позже:

public enum Foo
{
  INSTANCE;
  // adding another type here will cause a runtime

  static
  {
    if (Foo.values().length != 1)
    {
      throw new IllegalStateException("Not a Singleton.");
    }
  }
}

Ответ 5

Это потому, что ваше решение не является потокобезопасным.

Современный способ заключается в привязке экземпляра к значению enum:

enum Singleton {
    INSTANCE;
}

Если вы хотите использовать ленивый init экземпляра, вы можете использовать ClassLoader для обеспечения безопасности потока:

public class Singleton {
        private Singleton() { }

        private static class SingletonHolder { 
                public static final Singleton INSTANCE = new Singleton();
        }

        public static Singleton getInstance() {
                return SingletonHolder.INSTANCE;
        }
}

Дополнительная информация в Википедии

Ответ 6

Вероятно, он ищет этот ответ:

public class Singleton 
{
   private static Singleton ref;
   static
   {
       ref = new Singleton();
   }
   private Singleton()
   {
   }
   public static Singleton getInstance() 
   {
       return ref;
   }
}

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

Ответ 7

Синглтон, который я бы написал, будет выглядеть так:

@Service
class PersonService {
    // implementation here
}

Но мне также нравятся идеи enum. На самом деле, я никогда не пишу (и не нуждаюсь) синглтон, отличный от того, который был выше.

Ответ 8

Почему вы не можете просто

public class SingletonSandBox {

    private static SingletonSandBox instance = new SingletonSandBox();

    private SingletonSandBox(){
    }

    public static SingletonSandBox getInstance(){
        return instance;
    }

}

и тест

public static void main(String[] args) {

        SingletonSandBox sss1 = SingletonSandBox.getInstance();

        SingletonSandBox sss2 = SingletonSandBox.getInstance();

        System.out.println(sss1 == sss2);

}

Как я знаю, это потокобезопасно и короче, чем использование статического блока. Снова статическое объявление поля читается ранее по сравнению со статическим блоком по времени выполнения.

Ответ 9

Несколько раз нам может потребоваться создать один экземпляр подкласса, только как класс Java Toolkit. Пример 1. Создание одного экземпляра подкласса будет его разработка. См. Singleton Design Patter в Java

Ответ 11

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

final class Singleton
{
    private static Object lock = new Object();
    private static volatile Singleton instance = null;
    private Singleton() { }
    public static Singleton getInstance()
    {
        if(instance == null)
        {
            synchronized(lock)
            {
                if(instance == null)
                {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Ответ 12

Эта реализация singleton называется,

ленивая инициализация

Но проблема в том, что эта реализация не является потокобезопасной.

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

Существует несколько других популярных реализаций Singleton. Один из них,

Ожидаемая инициализация

final class EagerIntializedSingleton  {
    private static final EagerIntializedSingleton instance = new EagerIntializedSingleton();
    private EagerIntializedSingleton (){}
    private static EagerIntializedSingleton getInsance() {
        return instance;
    }  
}

Но здесь экземпляр класса singleton создается во время загрузки класса. (Это стандартный одноэлементный класс, созданный IntelliJ IDE)

Следующая популярная реализация -

Инициализация статического блока

private static StaticBlockSingleton instance;
private StaticBlockSingleton(){}
static {
    try {
         instance = new StaticBlockSingleton();
    catch(Exception e) {
         .............
    }
}

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

Ответ 13

Это может быть потому, что он не использует " double-checked-locking" (как говорили другие), или это также может быть связано с тем, что очевидно, можно вызвать частный конструктор используя отражение (если это позволяет политика безопасности).

Чтобы вызвать конструктор без параметров, передайте пустой массив.

package org.example;

public class Singleton {

    private static final Object LOCK = new Object();
    private static final Singleton SINGLETON = new Singleton();
    private static volatile boolean init = false; // 'volatile' to prevent threads from caching state locally (prevent optimizing) 

    private Singleton() {
        synchronized (LOCK) {
            if( init == true) {
                throw new RuntimeException("This is a singleton class!");
            }
            init=true;
        }
    }

    public static Singleton obtainClassInstance() {
        return SINGLETON;
    }

}


package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class SimpleSingletonTester {

    /**
     * @param args
     * @throws NoSuchMethodException 
     * @throws SecurityException 
     * @throws InvocationTargetException 
     * @throws IllegalAccessException 
     * @throws InstantiationException 
     * @throws IllegalArgumentException 
     */
    public static void main(String[] args) throws SecurityException, NoSuchMethodException, 
    IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException 
    {

        Class[] parameterTypes = {};
        Object[] initargs = {};
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(parameterTypes);
        System.out.println( constructor.isAccessible() );
        constructor.setAccessible(true);
        System.out.println( constructor.isAccessible() );
        System.out.println( constructor.newInstance(initargs) );
        System.out.println( constructor.newInstance(initargs) );

    }

}