Який ефективний спосіб реалізувати однотонний візерунок на Java? [зачинено]


812

Який ефективний спосіб реалізувати однотонну схему в Java?


1
"Який ефективний спосіб реалізувати однотонний шаблон у Java?" будь ласка, визначте ефективний.
Marian Paździoch

medium.com/@kevalpatel2106/… . Це повна стаття про те, як досягти безпеки потоку, відображення та серіалізації в одиночному малюнку. Це хороше джерело для розуміння переваг та обмежень однокласного класу.
Кеваль Патель

Як зазначає Джошуа Блох в «Ефективній Java», найкращим способом є перерахунок синглів. Тут я класифікував різні реалізації як ліниві / нетерплячі тощо
isaolmez

Відповіді:


782

Використовуйте перелік:

public enum Foo {
    INSTANCE;
}

Джошуа Блох пояснив такий підхід у своїй Ефективній розмові на Java Reloaded на Google I / O 2008: посилання на відео . Також дивіться слайди 30-32 його презентації ( ефективні_java_reloaded.pdf ):

Правильний шлях реалізації серіалізаційного синглтона

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Edit: онлайн частина «Ефективне Java» , говорить:

"Цей підхід функціонально еквівалентний підходу на публічному полю, за винятком того, що він є більш стислим, забезпечує механізм серіалізації безкоштовно і забезпечує гарантію залізобезпечного захисту від багаторазових інстанцій, навіть за умови складних серіалізаційних або рефлексійних атак. Хоча цей підхід має ще не отримавши широкого поширення, одноелементний тип enum - найкращий спосіб реалізувати одинарний ".


203
Я думаю, що люди повинні почати дивитися на енуми як на просто клас із особливістю. якщо ви можете перелічити екземпляри свого класу під час компіляції, використовуйте enum.
Амір Арад

7
Я особисто часто не знаходжу необхідності використовувати одинарний малюнок безпосередньо. Я іноді використовую весняну ін'єкцію залежності залежно від контексту програми, який містить те, що воно називає одиночними. Мої класи утиліти, як правило, містять лише статичні методи, і мені не потрібні їхні екземпляри.
Стівен Денне

3
Привіт, Чи може хто-небудь сказати мені, як цей тип синглів можна знущатися та перевіряти в тестових випадках Я намагався поміняти підроблений екземпляр одного типу для цього типу, але не зміг.
Ashish Sharma

29
Я думаю, це має сенс, але мені все одно це не подобається. Як би ви створили синглтон, який розширює інший клас? Якщо ви використовуєте перерахунок, ви не можете.
chharvey

11
@bvdb: Якщо ви хочете отримати велику гнучкість, ви вже здригнулися, вперше застосувавши одиночку. Можливість створити незалежний екземпляр, коли вам це потрібно, сама по собі є безцінною.
cHao

233

Залежно від використання існує кілька «правильних» відповідей.

Оскільки java5 найкращий спосіб зробити це - використовувати enum:

public enum Foo {
   INSTANCE;
}

Перед java5, найпростіший випадок:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

Перейдемо до коду. По-перше, ви хочете, щоб клас був остаточним. У цьому випадку я використав finalключове слово, щоб повідомити користувачам, що воно остаточне. Тоді вам потрібно зробити конструктор приватним, щоб перешкодити користувачам створювати свій власний Foo. Викидання виключення з конструктора заважає користувачам використовувати відображення для створення другого Foo. Потім ви створюєте private static final Fooполе для утримування єдиного екземпляра та public static Foo getInstance()спосіб його повернення. Специфікація Java гарантує, що конструктор викликається лише при першому використанні класу.

Якщо у вас дуже великий об'єкт або важкий будівельний код І також є інші доступні статичні методи або поля, які можуть бути використані перед необхідним екземпляром, тоді і лише тоді вам потрібно використовувати ініціальну ліниву.

Ви можете використовувати a private static classдля завантаження екземпляра. Код виглядатиме так:

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

Оскільки рядок private static final Foo INSTANCE = new Foo();виконується лише тоді, коли насправді використовується клас FooLoader, це піклується про ледачий екземпляр, і чи гарантовано він захищений потоком.

Якщо ви також хочете мати можливість серіалізувати ваш об'єкт, вам потрібно переконатися, що десеріалізація не створить копію.

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Метод readResolve()забезпечить повернення єдиного екземпляра, навіть коли об'єкт був серіалізований у попередньому запуску вашої програми.


32
Перевірка на рефлексію марна. Якщо інший код використовує роздуми про приватних осіб, це Game Over. Немає жодних підстав навіть намагатися правильно функціонувати при таких зловживаннях. І якщо ви спробуєте, то це буде неповний "захист" у будь-якому випадку, просто багато витраченого коду.
Wouter Coekaerts

5
> "По-перше, ви хочете, щоб клас був остаточним". Чи може хтось детальніше розглянути це?
PlagueHammer

2
Захист від десеріалізації повністю порушений (я думаю, це згадується в «Ефективній Java 2nd Ed»).
Том Хоутін - тайклін

9
-1 це абсолютно не найпростіший випадок, він надуманий і непотрібний. Подивіться на відповідь Джонатана щодо насправді найпростішого рішення, якого достатньо у 99,9% усіх випадків.
Майкл Боргвардт

2
Це корисно, коли вашому одиночному особі потрібно успадкувати суперклас. У цьому випадку ви не можете використовувати шаблони enum singleton, оскільки enums не можуть мати надкласовий клас (хоча вони можуть реалізувати інтерфейси). Наприклад, Google Guava використовує статичне остаточне поле, коли шаблон однотонного перерахунку не є опцією: code.google.com/p/guava-libraries/source/browse/trunk/guava/src/…
Етьєн Неве

139

Відмова: Я щойно узагальнив усі приголомшливі відповіді і написав це своїми словами.


Під час реалізації Singleton у нас є 2 варіанти
1. Ледача завантаження
2. Рання завантаження

Ледаче завантаження додає біт накладних витрат (багато, якщо чесно), тому використовуйте його лише тоді, коли у вас є дуже великий об'єкт або важкий будівельний код. А також є інші доступні статичні методи або поля, які можуть бути використані до того, як потрібен екземпляр, тоді і лише тоді вам потрібно використовувати ліниву ініціалізацію. Інакше вибір раннього завантаження - хороший вибір.

Найбільш простим способом реалізації Singleton є

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }
}

Все добре, крім раннього завантаження сингла. Давайте спробуємо ліниво завантажений синглтон

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

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

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

але цього недостатньо для захисту героя, Дійсно !!! Це найкраще, що ми можемо / повинні зробити, щоб допомогти нашому герою

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

Це називається "Ідентифікація подвійної перевірки блокування". Забути мінливе твердження легко і важко зрозуміти, чому це необхідно.
Детальніше: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Тепер ми впевнені в злій нитці, а як же жорстока серіалізація? Ми повинні переконатися, що навіть під час десеріалізації не створюється новий об'єкт

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

Метод readResolve()забезпечить повернення єдиного екземпляра, навіть коли об'єкт був серіалізований у попередньому запуску нашої програми.

Нарешті ми додали достатній захист від потоків і серіалізації, але наш код виглядає об'ємно і некрасиво. Давайте давайте нашому герою заробляти

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Так, це наш самий герой :)
Оскільки рядок private static final Foo INSTANCE = new Foo();виконується лише тоді, коли клас FooLoaderфактично використовується, це піклується про ледачу інстанцію,

і чи гарантовано це безпечно для ниток.

І ми зайшли так далеко, ось найкращий спосіб досягти всього, що ми зробили найкращим чином

 public enum Foo {
       INSTANCE;
   }

До якого внутрішньо будуть ставитися як

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

Це воно! Більше немає страху перед серіалізацією, нитками та потворним кодом. Також однолітні ENUMS ліниво ініціалізуються .

Цей підхід функціонально еквівалентний підходу на публічному полю, за винятком того, що він більш стислий, забезпечує механізм серіалізації безкоштовно та забезпечує гарантію залізобезпеки проти багаторазових інстанцій, навіть за умови складних серіалізаційних або рефлексійних атак. Хоча цей підхід ще не отримав широкого поширення, одноелементний тип enum - найкращий спосіб реалізувати синглтон.

-Joshua Блох в "Ефективна Java"

Тепер ви, можливо, зрозуміли, чому ENUMS вважається найкращим способом впровадження Singleton і дякую за ваше терпіння :)
Оновили це у своєму блозі .


3
Просто уточнення: одиночні кнопки, реалізовані за допомогою enum, ініціалізуються ліниво. Подробиці тут: stackoverflow.com/questions/16771373 / ...

2
чудова відповідь. останнє, переосмислити метод клонування, щоб викинути виняток.
riship89

2
@xyz приємні пояснення, мені дуже сподобалось та дізнався дуже легко, і я сподіваюся, що ніколи цього не забував
Premraj

1
Один з найкращих відповідей, які я коли-небудь помічав на stackoverflow. Дякую!
Шай Цадок

1
Там є проблема сериализации за допомогою перерахувань як Сінглтон: Будь-які значення поля - члени НЕ серіалізовать і , отже , не відновлюються. Див. Специфікацію серіалізації об'єктів Java, версія 6.0 . Ще одна проблема: Немає версій - всі види перерахувань мають фіксований serialVersionUIDз 0L. Третя проблема: немає налаштування: Будь-які специфічні для класу записиObject, readObject, readObjectNoData, writeReplace та readResolve методи, визначені типами enum, ігноруються під час серіалізації та десеріалізації.
Василь Бурк

124

Рішення, розміщене Стю Томпсоном, дійсне в Java5.0 та пізніших версіях. Але я вважаю за краще не використовувати його, оскільки вважаю, що це схильність до помилок.

Забути мінливе твердження легко і важко зрозуміти, чому це необхідно. Без енергонезалежності цей код уже не був би безпечним для потоків через подвійну перевірку блокування антипаттера. Детальніше про це див. У пункті 16.2.4 Java Concurrency in Practice . Коротше кажучи: цей шаблон (до Java5.0 або без мінливого твердження) може повернути посилання на об'єкт "Бар", який (досі) знаходиться у неправильному стані.

Ця модель була придумана для оптимізації продуктивності. Але це насправді вже не є справжнім питанням. Наступний лінивий код ініціалізації - швидше і - що ще важливіше - легше читати.

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}

1
Справедливо! Мені просто комфортно з летючою та її використанням. О, і три ура для JCiP.
Стю Томпсон

1
О, це, мабуть, підхід, який пропагував Вільям П'ю, про славу FindBugz.
Стю Томпсон

4
@Stu Перше видання «Ефективна Java» (авторське право 2001 р.) Детально
розглядає

9
@Bno: А як зробити приватний конструктор?
xyz

2
@ AlikElzin-kilaka Не зовсім. Екземпляр створюється у фазі завантаження класу для BarHolder , яка затримується до першого необхідного. Конструктор бару може бути настільки складним, як ви хочете, але його не зателефонують до першого getBar(). (І якщо вас getBarназивають "занадто рано", тоді ви зіткнетеся з тією самою проблемою, незалежно від того, як реалізуються одиноки.) Ви можете побачити ліниве завантаження класу кодом вище тут: pastebin.com/iq2eayiR
Ti Strga

95

Нитка безпечна в Java 5+:

class Foo {
    private static volatile Bar bar = null;
    public static Bar getBar() {
        if (bar == null) {
            synchronized(Foo.class) {
                if (bar == null)
                    bar = new Bar(); 
            }
        }
        return bar;
    }
}

EDIT : Зверніть увагу на volatileмодифікатор тут. :) Це важливо, оскільки без нього JMM (Java Memory Model) не гарантує інші потоки, щоб побачити зміни його значення. Синхронізація цього не бере на себе, вона лише серіалізує доступ до цього блоку коду.

EDIT 2 : У відповіді @Bno детально описаний підхід, рекомендований Біллом П'ю (FindBugs), і це можна суперечити краще. Ідіть читайте і голосуйте також за його відповідь.


1
Де я можу дізнатися більше про модифікатор летючого складу?
одинадцять81


2
Я думаю, що важливо згадати про атаки рефлексії. Правда, що більшості розробників не потрібно турбуватися, але, схоже, такі приклади (над синглами на основі Enum) повинні включати або код, який захищає від атак з декількома інстанціями, або просто ставити застереження, що вказує на такі можливості.
luis.espinal

2
Летючі ключові слова тут не потрібні - оскільки синхронізація дає як взаємне виключення, так і видимість пам'яті.
Гемант

2
Навіщо турбуватися з усім цим у Java 5+? Я розумію, що підхід enum забезпечує як безпеку нитки, так і ледачу ініціалізацію. Це також набагато простіше ... Крім того, якщо ви хочете уникнути занурень, я все-таки перешкоджаю вкладенню підходу статичного класу ...
Александрос

91

Забути ліниву ініціалізацію , це занадто проблематично. Це найпростіше рішення:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}

21
Змінна екземпляра однотонної також може бути остаточною. наприклад, приватний статичний кінцевий A одиночний = новий A ();
jatanp

19
Це фактично є лінивою ініціалізацією, оскільки статичний синглет не буде інстанціюватися, поки клас не завантажений і клас не буде завантажений, поки це не буде потрібно (що буде правильним про час, коли ви вперше посилаєтеся на метод getInstance ()).
Дан Дайер

7
Якщо клас A дійсно завантажується перед тим, як статичний екземпляр статики, ви можете обернути статику в статичний внутрішній клас, щоб відключити ініціалізацію класу.
Том Хотін - тайклін

3
Я згоден, ця відповідь найпростіша, а Анірудхан, немає потреби оголошувати цей примірник остаточним. Жоден інший потік не отримає доступ до класу під час ініціалізації статичних членів. це гарантується компілятором, іншими словами, вся статична ініціалізація робиться синхронізовано - лише одна нитка.
inor

4
Цей підхід має одне обмеження: конструктор не може кинути виняток.
wangf

47

Переконайтеся, що вам це справді потрібно. Зробіть Google для "однотонного анти-шаблону", щоб побачити деякі аргументи проти цього. Я думаю, що в цьому немає нічого поганого, але це лише механізм виявлення глобальних ресурсів / даних, тому переконайтеся, що це найкращий спосіб. Зокрема, я вважав, що введення залежності є більш корисним, особливо якщо ви також використовуєте одиничні тести, оскільки DI дозволяє використовувати знущаються ресурси для тестування.


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

21

Мене містифікують деякі відповіді, які пропонують DI як альтернативу використанню одиночних клавіш; це непов'язані поняття. Ви можете використовувати DI для введення екземплярів як одиночного, так і не одиночного (наприклад, за один потік). Принаймні це справедливо, якщо ви використовуєте Spring 2.x, я не можу говорити про інші рамки DI.

Тож моя відповідь на ОП була б (у всіх, окрім найбільш тривіального зразкового коду):

  1. Тоді використовуйте рамку DI, як Весна
  2. Зробіть це частиною вашої конфігурації DI, незалежно від того, чи є ваші залежності одиночними кнопками, обсяг запиту, масштаб сеансу чи інше.

Цей підхід дає вам приємну роз'єднану (і, отже, гнучку та перевірену) архітектуру, де використання синглетона - це легко оборотна деталь реалізації (за умови, звичайно, що будь-які одинакові кнопки, які ви використовуєте, є безпечними для потоків).


4
Можливо тому, що люди не згодні з вами. Я не прихилявся до вас, але я не погоджуюся: я думаю, що DI можна використовувати для вирішення тих же проблем, що й одиночні. Це ґрунтується на розумінні поняття "одиночний", що означає "об'єкт з єдиним екземпляром, до якого звертається безпосередньо глобальне ім'я", а не просто "об'єкт з одним екземпляром", що, можливо, трохи хитро.
Том Андерсон

2
Щоб трохи розширити, розгляньте, TicketNumbererякий повинен мати єдиний глобальний екземпляр, і де ви хочете написати клас, TicketIssuerякий містить рядок коду int ticketNumber = ticketNumberer.nextTicketNumber();. У традиційному одинарному мисленні попередній рядок коду мав би бути чимось подібним TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. У міркуванні DI, клас мав би такий конструктор public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.
Том Андерсон

2
І стає чужою проблемою викликати цього конструктора. Рамка DI зробила б це якоюсь глобальною картою; побудована вручну архітектура DI зробила б це, оскільки mainметод програми (або один із її міньйонів) створив би залежність, а потім викликав конструктор. По суті, використання глобальної змінної (або глобального методу) є просто простою формою жахливого шаблона локатора обслуговування , і його можна замінити ін'єкцією залежності, як і будь-яке інше використання цього шаблону.
Том Андерсон

@TomAnderson Я дуже розгублений з приводу того, чому люди "бояться" схему локатора служб. Я думаю, що в більшості випадків це надмірно чи не потрібно в кращому випадку, однак, є, здавалося б, корисні випадки. З меншою кількістю парам DI, безумовно, кращим, але уявіть собі 20+. Скажімо, що код не структурований, це не коректний аргумент, тому що іноді групування параметрів просто не має сенсу. Крім того, з точки зору тестування одиниці, я не дбаю про тестування сервісу, лише бізнес-логіку його, і якщо це правильно закодовано, то це було б просто. Цю потребу я бачив лише у дуже масштабних проектах.
Брендон Лінг

20

Дійсно, подумайте, для чого вам потрібен одинар, перш ніж писати його. Існує квазірелігійна суперечка щодо їх використання, яку ви можете легко наткнутися, якщо ви перейдете на Google в одиночку.

Особисто я намагаюся уникати синглів якомога частіше з багатьох причин, знову ж таки більшість з них можна знайти, гуглюючи однотонними. Я відчуваю, що досить часто одинаки зловживають, тому що їх легко зрозуміти всім, вони використовуються як механізм отримання "глобальних" даних в проект OO, і вони використовуються, тому що легко обійти управління життєвим циклом об'єкта (або дійсно думаючи про те, як можна зробити A зсередини B). Подивіться на такі речі, як Інверсія управління (IoC) або залежність впорскування (DI) для приємного середнього рівня.

Якщо вам справді потрібен, то у Вікіпедії є хороший приклад правильної реалізації сингтона.


Домовились. Це більше основоположний клас, який починає решту вашої програми, і якщо вона була продубльована, ви закінчитеся повним хаосом (тобто, єдиним доступом до ресурсу або забезпеченням безпеки). Передача глобальних даних по всій програмі - це великий червоний прапор. Використовуйте його, коли визнаєте, що вам це справді потрібно.
Сальвадор Валенсія

16

Далі 3 різних підходу

1) Енум

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) Перевірена блокування / ледаче завантаження

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3) Статичний заводський метод

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

13

Я використовую Spring Framework для управління своїми однотонними. Це не примушує "однотонність" класу (чого ви дійсно не можете зробити, якщо задіяні кілька завантажувачів класів), але забезпечує дійсно простий спосіб побудови та налаштування різних заводів для створення різних типів об'єктів.


11

У Вікіпедії є кілька прикладів синглів, також на Java. Реалізація Java 5 виглядає досить повною та безпечною для потоків (застосовано подвійне перевірене блокування).


11

Версія 1:

public class MySingleton {
    private static MySingleton instance = null;
    private MySingleton() {}
    public static synchronized MySingleton getInstance() {
        if(instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }
}

Ледаче завантаження, безпека ниток із блокуванням, низька продуктивність через synchronized.

Версія 2:

public class MySingleton {
    private MySingleton() {}
    private static class MySingletonHolder {
        public final static MySingleton instance = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHolder.instance;
    }
}

Ледаче завантаження, безпека різьби з не блокуванням, висока продуктивність.


10

Якщо вам не потрібно ледаче завантаження, просто спробуйте

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

    private Singleton() {}

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

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

Якщо ви бажаєте ледачого завантаження і хочете, щоб ваш Singleton був безпечним для потоків, спробуйте подвійну перевірку

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

        public static Singleton getInstance() { 
              if(null == instance) {
                  synchronized(Singleton.class) {
                      if(null == instance) {
                          instance = new Singleton();
                      }
                  }
               }
               return instance;
        }

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

Оскільки модель подвійної перевірки не гарантовано працює (через певну проблему з компіляторами, я нічого про це не знаю.), Ви також можете спробувати синхронізувати весь метод getInstance або створити реєстр для всіх ваших Singletons.


2
Перша версія найкраща. Якщо припустити, що клас не робить нічого іншого, крім надання синглтона, то він, як правило, інстанціюється приблизно в тій же точці, що і у другій версії через ледачу завантаження класу.
Дан Дайер

1
Подвійна перевірка безглуздо для статики. І чому ви оприлюднили метод захищеного клонування?
Том Хотін - тайклін

1
-1 ваша версія подвійного перевіреного блокування зламана.
assylias

5
Також вам потрібно зробити свою volatile
одиночну

Перша версія - лінива та безпечна для ниток.
Miha_x64

9

Я б сказав, що Енум одинарний

Синглтон, використовуючи enum в Java, як правило, спосіб оголосити enum singleton. Enum singleton може містити змінну екземпляра та метод екземпляра. Для простоти також зауважте, що якщо ви використовуєте будь-який метод примірника, вам потрібно забезпечити безпеку потоку цього методу, якщо він взагалі впливає на стан об'єкта.

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

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

Ви можете отримати доступ до нього Singleton.INSTANCEнабагато простіше, ніж викликати getInstance()метод на Singleton.

1.12 Серіялізація констант Енума

Константи Енума серіалізуються інакше, ніж звичайні об'єкти, що серіалізуються, або екстерналізації. Серіялізована форма константи перерахунку складається виключно з її назви; Польові значення константи у формі відсутні. Щоб серіалізувати константу enum, ObjectOutputStreamзаписує значення, повернене методом імені константи enum. Щоб дезаріалізувати константу перерахунку, ObjectInputStreamчитає ім'я постійної з потоку; деріаріалізована константа потім отримується за допомогою виклику java.lang.Enum.valueOfметоду, передаючи тип перерахунку константи разом з отриманим постійним ім'ям в якості аргументів. Як і інші об'єкти, що серіалізуються, або константи, що перебувають у процесі екстерналізації, константи перерахунку можуть функціонувати як цілі зворотних посилань, що з'являються згодом у потоці серіалізації.

Процес , з допомогою якого серіалізовать перерахування константи не може бути налаштований: будь-який клас конкретним writeObject, readObject, readObjectNoData, writeReplace, і readResolveметоди , визначені типами перерахувань ігноруються під час сериализации і десеріалізациі. Аналогічно, будь-які serialPersistentFieldsабо serialVersionUIDпольові декларації також ігноруються - всі типи перерахувань мають фіксовану serialVersionUIDз 0L. Документування серіалізаційних полів та даних для типів перерахувань не є необхідним, оскільки не змінюється тип надісланих даних.

Цитується з документів Oracle

Ще одна проблема звичайних Singletons полягає в тому, що коли ви реалізуєте Serializableінтерфейс, вони більше не залишаються Singleton, оскільки readObject()метод завжди повертає новий екземпляр, як конструктор на Java. Цього можна уникнути, використовуючи readResolve()та відкидаючи новостворений екземпляр, замінивши на одиночний, як показано нижче

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

Це може стати ще складнішим, якщо ваш Singleton Class підтримуватиме стан, як вам потрібно зробити їх перехідними, але в Enum Singleton, серіалізація гарантована JVM.


Добре читайте

  1. Однотонний візерунок
  2. Енуми, синглтони та десеріалізація
  3. Перевірено блокування та візерунок Singleton

8
There are 4 ways to create a singleton in java.

1- eager initialization singleton

    public class Test{
        private static final Test test = new Test();
        private Test(){}
        public static Test getTest(){
            return test;
        }
    

2- lazy initialization singleton (thread safe)

    public class Test {
         private static volatile Test test;
         private Test(){}
         public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();
                }
            }
         }

        return test;
    


3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)

    public class Test {

        private Test(){}

        private static class TestHolder{
            private static final Test test = new Test();
        }

        public static Test getInstance(){
            return TestHolder.test;
        }
    }

4- enum singleton
      public enum MySingleton {
        INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}

(1) не прагне, він лінивий завдяки механізму завантаження класу JVM.
Miha_x64

@ Miha_x64, коли я сказав, що прагнуть завантаження, я сказав, що прагне ініціалізація. Якщо ви думаєте, що обидва однакові, то те, що прагне завантаження?
Dheeraj Sachan

Ефективна Java - це чудова книга, але, безумовно, потребує редагування.
Miha_x64

@ Miha_x64, що прагне завантаження, чи можна пояснити на прикладі
Dheeraj Sachan

Робити щось "нетерпляче" означає "якнайшвидше". Наприклад, Hibernate охоче підтримує відносини з завантаженням, якщо це вимагається прямо.
Miha_x64

8

Можливо, у цій грі трохи запізнюється, але є багато нюансів щодо реалізації одиночного. Шаблон тримача не можна використовувати в багатьох ситуаціях. І IMO при використанні летючої - вам також слід використовувати локальну змінну. Почнемо спочатку і переглянемо проблему. Ви побачите, що я маю на увазі.


Перша спроба може виглядати приблизно так:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

Тут ми маємо клас MySingleton, який має приватний статичний член під назвою INSTANCE, і публічний статичний метод, який називається getInstance (). Перший раз, коли викликається getInstance (), член INSTANCE є нульовим. Потім потік перейде до умови створення та створить новий екземпляр класу MySingleton. Подальші виклики до getInstance () виявлять, що змінна INSTANCE вже встановлена, і тому не створить інший екземпляр MySingleton. Це гарантує, що існує лише один екземпляр MySingleton, який поділяється між усіма абонентами getInstance ().

Але ця реалізація має проблеми. Багатопотокові програми матимуть умову гонки щодо створення єдиного екземпляра. Якщо декілька потоків виконання одночасно потрапляють на метод getInstance () (або навколо), кожен з них буде бачити член INSTANCE як нульовий. Це призведе до того, що кожен потік створює новий екземпляр MySingleton та згодом встановлює член INSTANCE.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Тут ми використовували ключове слово синхронізований у підписі методу для синхронізації методу getInstance (). Це, безумовно, виправить наш стан гонки. Нитки тепер блокуються та вводять метод по черзі. Але це також створює проблеми з продуктивністю. Ця програма не тільки синхронізує створення єдиного примірника, але синхронізує всі виклики до getInstance (), включаючи читання. Читання не потрібно синхронізувати, оскільки вони просто повертають значення INSTANCE. Оскільки читання становлять основну частину наших дзвінків (пам’ятайте, що екземпляри трапляються лише під час першого дзвінка), ми спричинимо непотрібну ефективність, синхронізувавши весь метод.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

Тут ми перенесли синхронізацію від підпису методу до синхронізованого блоку, який завершує створення екземпляра MySingleton. Але чи вирішує це наша проблема? Ну, ми більше не блокуємо читання, але ми також зробили крок назад. Кілька потоків будуть потрапляти в метод getInstance () приблизно в один і той же час і приблизно, і всі вони побачать член INSTANCE як нульовий. Потім вони потраплять на синхронізований блок, де можна отримати замок і створити екземпляр. Коли цей потік вийде з блоку, інші потоки змагатимуться за замок, і одна за одною кожна нитка пройде через блок і створить новий екземпляр нашого класу. Тож ми повертаємось туди, з чого почали.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Тут ми видаємо черговий чек від блоку INSIDE. Якщо член INSTANCE вже встановлений, ми пропустимо ініціалізацію. Це називається подвійним перевіреним блокуванням.

Це вирішує нашу проблему множинних інстанцій. Але ще раз наше рішення поставило ще один виклик. Інші потоки можуть не "бачити", що член INSTANCE був оновлений. Це пояснюється тим, як Java оптимізує операції з пам'яттю. Нитками копіюються вихідні значення змінних з основної пам'яті в кеш процесора. Зміни значень потім записуються в цей кеш і читаються з нього. Це особливість Java, призначена для оптимізації продуктивності. Але це створює проблему для нашої синхронної реалізації. Другий потік - обробляється іншим процесором або ядром, використовуючи інший кеш - не побачить змін, внесених першим. Це призведе до того, що другий потік побачить член INSTANCE як нульовий примушує створити новий екземпляр нашого сингтона.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Ми вирішуємо це за допомогою мінливого ключового слова в декларації члена ІНСТАНЦІЇ. Це дозволить компілятору завжди читати з головної пам’яті та записувати її в основну пам'ять, а не кеш процесора.

Але ця проста зміна приходить дорожче. Оскільки ми обходимо кеш-пам'ять процесора, ми будемо приймати показник продуктивності кожного разу, коли ми працюємо з летючим членом INSTANCE - що ми робимо 4 рази. Ми ще раз перевіряємо існування (1 і 2), встановлюємо значення (3), а потім повертаємо значення (4). Можна стверджувати, що цей шлях є випадком бахроми, оскільки ми створюємо екземпляр лише під час першого виклику методу. Можливо, ударний твір на творчість є допустимим. Але навіть наш основний випадок використання, читається, буде працювати на летючий член двічі. Раз перевірити існування і знову повернути його значення.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

Оскільки хіт продуктивності пов’язаний з функціонуванням безпосередньо на летючому елементі, давайте встановимо локальну змінну до значення летючого та замість цього будемо працювати на локальній змінній. Це зменшить кількість разів, коли ми працюємо на енергонезалежних, тим самим відновивши частину втраченої роботи. Зауважте, що нам доведеться знову встановити нашу локальну змінну, коли ми входимо в синхронізований блок. Це гарантує, що це в курсі будь-яких змін, які відбулися, поки ми чекали блокування.

Я недавно написав статтю про це. Деконструкція Сінглтона . Ви можете знайти більше інформації про ці приклади та приклад шаблону "власника" там. Існує також приклад у реальному світі, що демонструє перевірений непостійний підхід. Сподіваюсь, це допомагає.


Чи можете ви поясніть, чому BearerToken instanceу вашій статті немає static? А що це result.hasExpired()?
Воланд

А як щодо class MySingleton- може, так і має бути final?
Воланд

1
@Woland BearerTokenЕкземпляр не є статичним, оскільки він є частиною, BearerTokenFactory яка налаштована на певний сервер авторизації. BearerTokenFactoryОб'єктів може бути багато - кожен має власний "кеш-пам'ять", BearerTokenякий він роздає, поки термін його дії не закінчиться. hasExpired()Метод на BeraerTokenназиваються в фабриках get()метод , щоб гарантувати її не роздає втрачає маркер. Якщо термін дії закінчився, на сервер авторизації буде запрошено новий маркер. Абзац після кодового блоку пояснює це детальніше.
Майкл Ендрюс

4

Ось як реалізувати просте singleton:

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */ 
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Ось як правильно лінь створити своє singleton:

public class Singleton {
    // The constructor must be private to prevent external instantiation   
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only 
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE 
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

Обидва ледачі, припускаючи, що єдине, що вам потрібно від одиночного, - це його екземпляр.
Miha_x64

@ Miha_x64 перший випадок інстанціює синглтон, коли JVM ініціалізує клас, другий випадок буде інстанціювати синглтон при виклику getInstance(). Але дійсно, якщо у вас немає будь-яких інших статичних методів у вашому класі, Singletonі ви лише дзвоните getInstance(), реальної різниці немає.
Ніколя Філотто

3

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

Крім того, якщо синглтон повинен бути серіалізним, всі інші поля повинні бути перехідними, а метод readResolve () повинен бути реалізований для підтримки єдиного інваріантного об'єкта. В іншому випадку, кожного разу, коли об'єкт дезаріалізується, буде створений новий екземпляр об'єкта. Що readResolve () робить, це замінити новий об’єкт, прочитаний readObject (), який змусив цей новий об'єкт збирати сміття, оскільки немає змінної, що посилається на нього.

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 

3

Різні способи зробити одиночний об’єкт:

  1. За Джошуа Блохом - Енум був би найкращим.

  2. ви також можете використовувати подвійне блокування перевірки.

  3. Можна використовувати навіть внутрішній статичний клас.


3

Енум одиночний

Найпростішим способом реалізації Singleton, який є безпечним для потоків, є використання Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Цей код працює з моменту впровадження Enum в Java 1.5

Перевірено блокування

Якщо ви хочете кодувати "класичний" синглтон, який працює у багатопотоковому середовищі (починаючи з Java 1.5), слід скористатися цим.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class){
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance ;
  }
}

Це не є безпечним для потоків до 1,5, оскільки реалізація летючого ключового слова була різною.

Раннє завантаження Singleton (працює навіть до Java 1.5)

Ця реалізація створює одиночне зображення при завантаженні класу та забезпечує безпеку потоку.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}

2

Для JSE 5.0 і вище використовуйте підхід Enum, в іншому випадку використовуйте статичний підхід для одноразового тримання ((ледачий підхід для завантаження, описаний Біллом П'ю). Шкільне рішення також є безпечним для потоків, не вимагаючи спеціальних мовних конструкцій (тобто мінливих або синхронізованих).


2

Інший аргумент, який часто застосовується проти Сінглтон, - це їх проблеми зі встановленням. Для проведення тестування одинарних нелегко піддаються вибору. Якщо це виявляється проблемою, я люблю вносити такі незначні зміни:

public class SingletonImpl {

    private static SingletonImpl instance;

    public static SingletonImpl getInstance() {
        if (instance == null) {
            instance = new SingletonImpl();
        }
        return instance;
    }

    public static void setInstance(SingletonImpl impl) {
        instance = impl;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

Доданий setInstanceметод дозволяє встановити макет виконання одного класу під час тестування:

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Це також працює з підходами ранньої ініціалізації:

public class SingletonImpl {

    private static final SingletonImpl instance = new SingletonImpl();

    private static SingletonImpl alt;

    public static void setInstance(SingletonImpl inst) {
        alt = inst;
    }

    public static SingletonImpl getInstance() {
        if (alt != null) {
            return alt;
        }
        return instance;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Це має і недолік піддавати цю функціональність звичайному додатку. Інші розробники, що працюють над цим кодом, можуть спокусити використати метод «setInstance», щоб змінити певну функцію і, таким чином, змінити всю поведінку програми, тому цей метод повинен містити принаймні гарне попередження у своєму javadoc.

Тим не менш, для можливості тестування макета (при необхідності) ця експозиція коду може бути прийнятною ціною.


1

найпростіший одиночний клас

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}

1
це те ж саме , як відповідь Джонатана нижче
Inor

1
Дублікат цього родинного відповіді по Іонафан відправив п'ять років тому. Дивіться цю відповідь для цікавих коментарів.
Василь Бурк

0

Я все ще думаю, що після java 1.5, enum є найкращою доступною синглтонною реалізацією, оскільки це також забезпечує те, що навіть у багатопотокових середовищах створюється лише один екземпляр.

public enum Singleton{ INSTANCE; }

і ви зробили !!!


1
Про це вже говорилося в інших відповідях років тому.
Панг

0

Погляньте на цю публікацію.

Приклади шаблонів дизайну GoF в основних бібліотеках Java

З розділу найкращої відповіді "Сінглтон",

Синглтон (впізнаваний за допомогою творчих методів, щоразу повертаючи один і той же екземпляр (як правило, сам))

  • java.lang.Runtime # getRuntime ()
  • java.awt.Desktop # getDesktop ()
  • java.lang.System # getSecurityManager ()

Ви також можете вивчити приклад Singleton з самих рідних класів Java.


0

Найкращий одинарний зразок, який я коли-небудь бачив, використовує інтерфейс постачальника.

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

Дивись нижче:

public class Singleton<T> implements Supplier<T> {

    private boolean initialized;
    private Supplier<T> singletonSupplier;

    public Singleton(T singletonValue) {
        this.singletonSupplier = () -> singletonValue;
    }

    public Singleton(Supplier<T> supplier) {
        this.singletonSupplier = () -> {
            // The initial supplier is temporary; it will be replaced after initialization
            synchronized (supplier) {
                if (!initialized) {
                    T singletonValue = supplier.get();
                    // Now that the singleton value has been initialized,
                    // replace the blocking supplier with a non-blocking supplier
                    singletonSupplier = () -> singletonValue;
                    initialized = true;
                }
                return singletonSupplier.get();
            }
        };
    }

    @Override
    public T get() {
        return singletonSupplier.get();
    }
}

-3

Іноді простого " static Foo foo = new Foo();" недостатньо. Подумайте лише про деякі основні вставки даних, які ви хочете зробити.

З іншого боку, вам доведеться синхронізувати будь-який метод, який створює змінну singleton як таку. Синхронізація не є поганою як такою, але вона може призвести до проблем продуктивності або блокування (у дуже рідкісних ситуаціях, використовуючи цей приклад. Рішення полягає в

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

Тепер що відбувається? Клас завантажується через завантажувач класів. Безпосередньо після інтерпретації класу з байтового масиву VM виконує статичний {} - блок. у цьому весь секрет: статичний блок викликається лише один раз, час, коли даний клас (ім'я) даного пакета завантажується цим завантажувачем одного класу.


3
Неправда. статичні змінні ініціалізуються разом зі статичними блоками при завантаженні класу. Не потрібно розділяти декларацію.
Крейг П. Мотлін

-5
public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException (“Already instantiated...”);
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

Оскільки ми додали ключове слово синхронізований перед getInstance, ми уникнули умови перегонів у випадку, коли два потоки викликають getInstance одночасно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.