13
December
2005

Паттерн Singleton (Одиночка)

Posted in: Паттерны проектирования |

English version of this article you can find here.
Этим постом я открываю цикл статей, посвященных паттернам проектирования. Все написанное мной основывается исключительно на личном опыте.
Паттерны проектирования - это описание некоторых проблем, возникающих во время объектно-ориентированного проектирования, а также способов их решения (как практических, так и теоретических). Иными словами - это примеры правильных подходов к решению типичных задач проектирования.
Одним из самых распространенных паттернов является Singleton (Одиночка). Задача этого паттерна ограничить количество экземпляров некоторого класса. Зачем это может понадобиться? Об этом читайте ниже. Реализовать же паттерн Singleton на языке Java можно следующим образом:

public final class Singleton {
    private static Singleton _instance = null;

    private Singleton() {}

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

Теперь приведу некоторые объяснения по поводу реализации.
Конструктор класса необходимо объявить с модификатором видимости private. Это предотвратит создание экземпляров класса как с помощью класса Singleton, так и с помощью его наследников. В связи с этим к объявлению класса смело можно дописать модификатор final.

Метод getInstance() создаст ровно один экземпляр класса Singleton. Этот метод объявлен как synchronized. Сделано это вот почему. В многопоточных программах при одновременном вызове метода getInstance() из нескольких потоков можно создать несколько экземпляров класса Singleton. А должен остаться только один!

От модификатора synchronized можно избавиться. Для этого _instance нужно проинициализировать:

private static final Singleton _instance = new Singleton(),

а в методе getInstance() убрать конструкцию “if”.

Но использование поздней инициализации (lazy initialization) предпочтительнее в случае, если создание экземпляра класса занимает много времени.

Вот собственно и все…

Ах, да! Зачем нам это нужно.
Мне приходится чаще всего использовать этот паттерн при работе с конфигурацией. Иногда конфигурацию программы удобно хранить в файле. Допустим, это будет простой текстовый файл “props.txt” со строками типа “ключ=значение”. Нам нужно гарантировать, что конфигурация в программе будет в единственном экземпляре. Вторую мы бы и так не создали, но нужно запретить это делать пользователю класса. Итак,

import java.util.*;
import java.io.*;

public class Configuration {
    private static Configuration _instance = null;

    private Properties props = null;

    private Configuration() {
         props = new Properties();
    	try {
	    FileInputStream fis = new FileInputStream(
                    new File(“props.txt”));
	    props.load(fis);
    	}
    	catch (Exception e) {
    	    // обработайте ошибку чтения конфигурации
    	}
    }

    public synchronized static Configuration getInstance() {
        if (_instance == null)
            _instance = new Configuration();
        return _instance;
    }

    // получить значение свойства по имени
    public synchronized String getProperty(String key) {
        String value = null;
        if (props.containsKey(key))
            value = (String) props.get(key);
        else {
            // сообщите о том, что свойство не найдено
        }
        return value;
    }
}

Теперь для работы с конфигурацией можно использовать конструкцию вида:

String propValue = Configuration.getInstance()
    .getProperty(propKey).

Если имена свойств в “props.txt” меняться не будут, можно описать их в классе таким образом:

public static final String PROP_KEY = “propKey”,

а значения получать так:

String propValue = Configuration.getInstance()
    .getProperty(Configuration.PROP_KEY).

Паттерн Singleton полезен не только при работе с конфигурациями. Его можно использовать также при написании ConnectionPool, Factory и других вещей.
Вот теперь точно все.

Related: Сколько нужно потоков для обработки пользовательских запросов или крайности при использовании Singleton

35 Comments »

RSS feed for comments on this post. TrackBack URI



Tatiana
February 26, 2006 #

Очень полезная страничка!! Спасибо!!

Igor
August 5, 2006 #

Спасибо. Написано доступно и интересно.

Oleg
November 30, 2006 #

Не плохо было бы убрать synchronized со всей функции, т.к. не очень optimized :)

Vile
December 12, 2006 #

Уже не первый раз Гугль кидает меня с моими вопросами именно сюда). Спасибо автору сайта.

Kat
February 6, 2007 #

а в какой момент выполняются все инициализации, которые до конструктора находятся? при загрузке класса? а что такое загрузка класса?

gdwz
April 10, 2007 #

А всетаки, зачем это нужно? В примере мы имеем объект, содержащий поле props. Почему бы не сделать props статическим полем и выдавать его статическим методом (в котором если надо производить инициализацию props)?

чувак
April 23, 2007 #

очень доходчивый пример, буквально таки на пальцах! Спасибо

Entry_N3
October 7, 2007 #

В книге GoF написано, что они этот паттерн в 90х холостяком называли (solitaire). :)

January 24, 2008 #

2Oleg: если ты имеешь в виду double checked synchronization, то это не работает и заслуживает отдельной статьи.
Если же имеется в виду использование synchronized блока, то это наоборот менее эффективно, чем синхронизированный метод.

February 14, 2008 #

Хороший пример, я раньше встречал какой-то невменяемый, а логика этого понятна.

radkat
February 29, 2008 #

Поясните, пожалуйста, почему не требуется synchronized на getProperty ?

Vlad
April 26, 2008 #

А какая необходимость в:
private static Singleton _instance = null; ?

В смысле, почему бы не делать ВСЕГДА :
private static Singleton _instance = new Singleton(); ?

andrew
June 1, 2008 #

2 Vlad
потому что, создание обьекта должно быть в методе getInstance()для того, чтобы создавался тока один обьект, ведь этот паттерн для этого и предназначен :)

Pavel
August 3, 2008 #

а почему бы не сделать:

private static final INSTANCE = new Singelton();

private Singelton(){…}

public static void getInstance(){
return INSTANCE;
}
принципиально то же самое что в примере, но немного изящнее и понятнее, и позволяет отказаться от проверки на null.

Vitaliy
August 4, 2008 #

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

Fevzi
August 29, 2008 #

Супер! В нете много статей и книг по паттернам, все они так зпутанно объясняют, а вот здесь кратко , с кодом да ещё,очень понятно и пример выбран понятный!
Автору респект!

mm
September 26, 2008 #

Автору космический THANKS!

November 7, 2008 #

Спасиба автору.Понимат легко.ищё жду такие стате

Olka
November 28, 2008 #

To Pavel, To Vlad:
Чтобы не выполнять логику конструктора раньше времени - то есть раньше первого обращения. А вдруг в конструкторе надо зачитать и обработать длинющий файл?…

Dmitry
January 8, 2009 #

To Olka
Всё равно статическое поле инициализируется только при загрузке класса, а класс загружается только при первом к нему обращении. Т.к. обращения к классу Configuration делаются только при необходимости прочитать конфигурацию, то вариант со статическими методами работал бы так же, как Singleton. Так что это не тот случай, когда без Синглтона нельзя обойтись (например, когда нужно наследоваться). Но, всё-таки его использование здесь - это хороший стиль.

Vad
February 1, 2009 #

Как-то некрасиво получается…а нельзя сделать один класс Singleton, а потом просто наследоваться от него, а не писать реализацию синглтона в каждом классе, использующем эту идиому снова и снова чтобы не дублировать код? Точно знаю, что в C++ так делается (правда, не без использования шаблона класса), что выглядит поизящнее…только, вот, насчёт Jav’ы не уверен.

sdv
February 26, 2009 #

Автор ошибся и это очевидно. При большой нагрузке (старт сотен потоков плюс долгая инициализация) вылезут проблемы.

Верное, классическое, решение - вот:

private volatile static Singleton instance;

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

Или private static final instance = new Singelton(); и дальше по тексту.

Отличия между этими решениями очевидны, но, боюсь, автор его не совсем осознает.

Как итог, я бы не стал доверять этому циклу статей. И вообще, ИМХО стоит читать нормальную документацию.

Не в обиду автору, мы все такими были.

sdv
February 26, 2009 #

Дабы на меня не обижались, дескать пришел, надул щеки и ушел, вот первая-же попавшаяся на глаза ссылка, в которой изложена суть вопроса

habrahabr.ru/blogs/complete_code/27108/

В общем, ИМХО, рано автору писать статьи

February 27, 2009 #

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

sdv
March 2, 2009 #

Простите, не обратил внимание на дату статьи.

Да, думаю между Вами в 2005-м и сегодняшним специалистом огромная разница.

Но все-же, пересмотрите статьи, которые Вы писали ранее, люди их смотрят.

March 2, 2009 #

Спасибо за Ваш отзыв. Постараюсь найти время для приведение в порядок ресурса.

March 22, 2009 #

Вот ссылка по теме, может кого-то заинтересует - http://www.javenue.info/post/83

jenik
July 3, 2009 #

знание патернов - унылое говно. Если хорошо знаешь среду и язык, так без знания патерна логично напишешь аналогию или копию за пару секунд.

July 6, 2009 #

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

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

lativ
August 21, 2009 #

2 sdv
В 2009 можно было бы и предложить вариант через AtomicReference:

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

private Singleton() {
}

public static final Singleton getInstance() {
if (SINGL_REF.get() == null) {
SINGL_REF.compareAndSet(null, new Singleton());
}
return SINGL_REF.get();
}
}

Arthur
September 19, 2009 #

Да, статьи очень хорошие.
Никто не может быть безупречен…
СПС
Люди их читают…мы в ответе за тех, кого приручили…Автор - учтите это:)

Сергей
October 4, 2009 #

2 sdv
Зачем нужно
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
Почему нельзя сделать
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
}
return instance;}???

Shender
October 19, 2009 #

спс на пальцах прям осталось применить

Алекс(Чёрный)
November 1, 2009 #

Костя, синглтон - это красивый способ обойти запрет на глобальные переменные. Кроме того в паттернах хватает других боков наравне с этим. Не мучай себя и других этим. Имхо авторы паттернов никогда не проводили должного исследования на предмет решения постулиуемых проблемм. Для этого нужно поднять около 100 проектов и чтоб 80 из них или около того дали устойчивый статистический результат. А кто это делал? Неужто не было нужды никогда выбрасывать написаные тобой паттерны и писать новые после изменения требования или ещё чего такого рода?

November 17, 2009 #

2 Алекс(Чёрный):

>> Неужто не было нужды никогда выбрасывать написаные тобой паттерны и писать новые после изменения требования или ещё чего такого рода?

Да постоянно так происходит. Ну и потом я не пишу паттернами. Просто стараюсь писать нормальный код и рефакторить, если что-то не нравится.
Потом что-то вырисовывается и это “что-то” иногда подпадает под описание какого-то паттерна.

Leave a comment

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong> <pre>