2011-01-16 3 views
8

Я нашел три способа создания экземпляра Singleton, но у меня есть сомнения относительно того, является ли любой из них лучшим. Я использую их в многопоточной среде и предпочитаю ленивый экземпляр.
Пример 1:java singleton instantiation

private static final ClassName INSTANCE = new ClassName(); 

public static ClassName getInstance() { 
    return INSTANCE; 
} 

Пример 2:

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

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

Пример 3:

private static ClassName INSTANCE; 

public static synchronized ClassName getInstance() 
{ 
    if (INSTANCE == null) 
     INSTANCE = new ClassName(); 

    return INSTANCE; 
} 

Проект Я использую ATM использует образец 2 везде, но я вроде как образец Еще 3. Существует также версия Enum, но я ее просто не понимаю.

Вопрос здесь - в каких случаях я должен/не должен использовать какие-либо из этих вариантов? Хотя я не ищу длительных объяснений (в этом много других тем, но все они в конечном итоге превращаются в аргументацию ИМО), я бы хотел, чтобы это было понятно с несколькими словами.

ответ

18

Самый безопасный и простой способ реализовать синглтон в Java является использование перечислений (как вы упомянули):

public enum ClassName { 
    INSTANCE; 

    // fields, setters and getters 
} 

перечисления семантика гарантирует, что будет только один INSTANCE

Если нет используя подход enum, вы должны позаботиться о довольно многих аспектах, таких как условия гонки и размышления. Я нарушал одиночные рамки некоторых рамок и злоупотреблял ими, потому что они были неправильно написаны. Перечисление гарантирует, что никто не сломает его.

+0

Но как его использовать? Например, у меня есть следующее: public class MyClass { public enum ClassName { INSTANCE; // Я помещаю все свои методы здесь, а не ... } // здесь, как обычно? } Редактировать: damn it, no форматирование комментариев? Ненавидеть это! – jurchiks

+1

Вы пишете свои однотонные поля и методы ниже. И затем получите доступ к ним через ClassName.INSTANCE.doSomething (foo) ' – Bozho

+0

@jurchiks: yes. В другом месте вы бы назвали 'ClassName.INSTANCE.methodName()', чтобы использовать методы singleton (или, возможно, использовать статический импорт, чтобы избавиться от 'ClassName.INSTANCE.'). – Carl

1

Образец 1: Используйте, если вам не нужен ленивый синглтон.
Образец 2: Никогда не используйте - это путает слишком много классов. И внутренний класс просто для хранения одной переменной кажется немного ненужным. Пример 3: Это ленивый синглтон. Используйте его, если вам это нужно.

2

Прежде всего, убедитесь, что вам нужен синглтон, и вы хотите обеспечить доступ к одноточечному доступу «глобального уровня». Я обнаружил, что во многих случаях клиентам синглтона не нужно знать, что это синглтон. Скорее, им просто нужно получить услугу, когда они создаются.

Таким образом, независимо от того, как вы получаете синглтон (если вообще), подумайте об изменении способа доступа ваших классов к этому объекту. Хотя это означает изменение конструкторов и изменение «изменений в распределении», я обнаружил, что рамки внедрения инъекций снижают стоимость. Рамка DI также может затем заботиться об однотональном экземпляре (например, Guice делает это).

Кроме этого. Вариант 3 является типичной и наиболее распространенной (и потокобезопасной) версией, с которой я знаком. Вариант 1 в основном предназначен для нелазной инициализации (не всегда приемлемой). Я никогда не видел 2 используемых.

+0

Ну, класс, который я хочу создать, содержит 2 объекта карты с возможным множеством данных, два метода для каждой карты для получения значения с помощью ключа (и заполнения значения, если он равен нулю), и двух методов, которые обращаются к базе данных (сначала для заполнения значение выбранного ключа, второе для вставки/обновления/удаления данных из базы данных с использованием существующих данных). Не считая геттеров/сеттеров здесь. Как вы думаете, это не должно быть одиноким? – jurchiks

+0

Я не уверен, что вы получаете ядро ​​того, что я предлагаю. Предположим, что вы инкапсулировали все, что вы только что описали, как интерфейс и соответствующий класс с именем DBLookupService и DBLookupServiceImpl. Для классов, которые используют LookupService, неважно, откуда оно взялось, - просто важно, чтобы они получили экземпляр LookupService. Это также значительно облегчает тестирование. – Uri

+0

Что касается синтаксиса - возможно, но вы могли бы подумать о многоядерном оптимизированном коде, в котором вы используете несколько экземпляров (например, если вы могли бы разделить данные осмысленно). Но я хочу сказать, что вы должны сделать все, чтобы избежать вызовов этого getInstance() из кода, использующего эту услугу. Это значительно облегчит тестирование и будущие изменения. – Uri

4

Образец 1 не использует ленивую инициализацию.

Образец 2 и 3 являются ленивыми. В примере 2 используется Initialization on demand holder idiom (IODH), у которого нет накладных расходов на синхронизацию. Поэтому он быстрее, чем Образец 3.

В эффективной Java (пункт 3) Джошуа Блох рекомендует, чтобы одноэлементный тип перечисления - лучший способ реализовать одноэлементный.

Однако, если вы не уверены в типе перечисления, придерживайтесь IODH.

+0

Почему образец 1 не использует ленивую инициализацию? Я думаю, что он используется, поскольку есть только один экземпляр, и выполняется одна инициализация. – Sam003