Pull to refresh

Comments 32

На тему «Singletone в JAVA» по топикам Хабра можно уже курсовик писать…
Вот именно! По-моему это не то, что нужно обсуждать. Об этом уже всё сказано — нужно просто брать правильную реализацию и использовать.
Прочтите внимательно упомянутые статьи в разрезе проблем каждого из предложенных в этих статьях решений
Надо писать «Правильную Реализацию»
Хотелось бы уточнить пару вопросов:
Где здесь управляемость создания объекта в блоке try-catch?
Зачем в блоке статической инициализации конструкция synchronized?
Постараюсь ответить за автора. Мне кажется, тут закрались вот какие ошибки:

1. try-catch кушает исключение. Это очень плохо, потому, что instance будет равен null и вызовет непредсказуемое исключение дальше в ходе исполнения программы. Проблема в том, дебажить будет крайне сложно: ошибка и симптом далеки друг от друга.

2. synchronized ни к чему. Блок статической синхронизации гарантировано исполняется лишь один раз. Об одновременном исполнении не может быть и речи.

3. volatile ни к чему. Значение instance меняться не будет (это же сингельтон), так что запрещать кеширование совершенно не нужно.

Мне кажется, что приведённый код очень опасен. Отработает он, похоже, правильно. Однако он может смутить читателя, поскольку синхронизация используется совершенно ни к месту.

Похоже, автор слышал о synchronized, volatile и статических переменных, но не понял их вполне.
Спасибо, хотелось бы чтобы ваш комментарий дошел до автора поста :)

По поводу ответа на 3й (мнимый) вопрос «зачем volatile?» вы совершенно правы, здесь он ни к чему, однако, известно откуда вообще взялся этот volatile. volatile появился в подходе «Double Checked Locking & volatile», почему не работает без volatile объясняется в статье «Правильный Singleton в Java». Там бяка кроется в первой проверке instance на null, которая не защищена конструкцией synchronized: может произойти такая ситуация, когда instance в другом потоке уже инициализирован указателем на объект, но сам объект еще не проинициализирован.
Я тоже подумал, что это какой-то плохо переваренный пример.

Ещё раз скажу, что этот пост очень опасен. Использование любых «волшебных слов» без понимания может привести к ошибкам. Однако многопоточные приложения особо каварны: у разработчика всё может работать хорошо, но в реальных условиях могут появится самые непредсказуемые и сложно диагностируемые ошибки.
)) А как же без потоков в многопоточной задаче?
1. try-catch там не для того, чтобы тупо съесть исключение, а чтобы иметь возможность сделать что-либо, если исключение возникло при инициализации объекта.

2. Блок статической инициализации, конечно, гарантировано исполняется лишь один раз, но не факт что гарантирует попытку инициализации лишь одним процессом, в случае, если два потока одновременно пытаются получить доступ к неинициализированному классу. О чём я и писал, но похоже, многие смотрят только пример и никогда не читают текст.

3. Еще раз повторюсь, что префикс volatile обеспечивает непротиворечивость значения переменной (ссылки) на объект-синглетон в разных потоках, для случаев, когда несколько потоков пытаются одновременно получить доступ к неинициализированному классу.
1. Однако, он тупо съедает исключение и пользовательский код его никогда не получит, это тоже самое что и если бы вы тело конструктора синглтона заключили в try{}catch(), блок статической инициализации теряет свой смысл.

2. Блок статической инициализации thread-safe.

3. Вариант исключается т.к. ссылка инициализируется в блоке статической инициализации, а он см. пункт2
3. Тогда всё намного проще и вариант по-любому рабочий и потокобезопасный
Безусловно рабочий, только функционально он ничем не отличается от варианта
public class Singleton {
    public static final Singleton instance = new Singleton();
}
Этот вариант прост и легко узнаваем. Ваш же необоснованно более запутанный, также
«использование volatile модификатора может привести к проблемам производительности на мультипроцессорных системах»
, а ведь использовать его нет никакой необходимости.
Ok!
Представим ситуацию, что конструктор класса Singleton или какие-либо инкапсулированные объекты-члены класса генерирует исключении при инициализации. Тогда что?
Если инициализировать синглетон в статическом инициализаторе, поместив инициализацию в блок try-catch, то хоть как-то можно обработать эту ситуацию, а иначе…
Как обработать? Всё что могло уже упало. Надо или перевыбрасывать исключение или подавлять его оставляя некорректно инициализированный объект.
Использование volatile модификатора может привести к проблемам производительности на мультипроцессорных системах при достаточно частых обращениях к переменным с модификатором volatile. Не надо об этом забывать!
Тем более, что во втором примере, эта проблема устранена.
Автор знаком с synchronized и volatile, но не был уверен в thread-safe статического блокаинициализации. Поэтому и в первом и во втором примере сильно подстраховался с volatile и synchronized.
Кстати, спасибо! Нигде не смог найти достоверной информации о потокобезопасности инициализатора.
Кажетс автор вообще не знаком с джавой даже на миимальном уровне. Не надо писать статьи, если в голове полный треш по тематике.
весьма аргументированное изречение
Чем отличается ваш первый вариант от предложенного по ссылке в статье:
public class Singleton {
    public static final Singleton instance = new Singleton();
}


Да и, вообще, к чему разговоры? Джошуа Блох — наш бог, а бог говорит:
public enum Singleton {
    instance;
}

И всё тут. Я лучше буду использовать проверенное решение от опытного программиста, чем ваше.

По вашим примерам: статичное поле гарантировано будет инициализировано один раз (по дизайну VM), тоже самое относится и к статическому блоку.

Кстати, объясните ловлю Exception'а в статичном блоке? Приложение сможет работать дальше без синглтона? Тем более, что нигде он не выбрасывается.
Беда в том, что многие читают статьи крайне невнимательно — по диагонали.
ENUM прекрасное решение! К тому же, «из коробки». Как говорится — «на вкус и цвет...»
В одной из статей жаловались, что при инициализации невозможно корректно обработать исключение, выстреленное при инициализации объекта-синглетона (например в конструкторе). Соответственно, обработка подобного исключения не должна находится в конструкторе. Как обработать исключение при инициализации объекта — дело вкуса разработчика.
Еще одним из «пожеланий» к возможным реализациям был как можно более быстрое получение ссылки на объект при обращении к методу getInstance.
Ваша проблема решается следующим образом:
1. Пишется обычный синглтон с полем initialized и методом init:
public enum Singleton {
    instance;

    private volatile boolean initialized = false;

    public synchronized void init(Parameter p1, Parameter p2) throw SomeException {
        if ( initialized ) {
            // do init work (some exceptions may occurr)
            initialized = true;
        }
    }
}

2. Метод Singleton#init вызывается явно в момент инициализации приложения. В случае, например, с web-приложением — это какой-нибудь javax.servlet.ServletContextListener. Здесь же, кстати, можно нормально обработать исключение и вывести его в лог, остановить web-сервер, прокинуть дальше RuntimeException и т.д., не находясь в статичном контексте. Переменную initialized, вообще, можно убрать если гарантируется единственный вызов метода Singleton#init или если повторная инициализация не страшна.

Тем самым мы отделили понятие инстанцирования синглтона (да, он будет инстанцирован правильно) и его инициализации.

Мне кажется — это намного понятнее и я в своём коде использую именно такой подход.
Исходя из того, что несколькими постами выше меня убедили в thread-safe статической инициализации, код вида:
public class Singleton {

private static Singleton instance;

static {
System.out.print(«Singleton class Initializator\r\n»);
try {
instance = new Singleton();
} catch (Exception e) {
/* — some workaround — */
System.out.print(«Exception occurred\r\n»);
}
}

public static Singleton getInstance() {
return instance;
}
}

будет также надежен, но гораздо более эффективен, за счет отсутствия модификатора volatile, резко снижающего производительность в случае частых обращений к геттеру.
Согласны?
Нет, у меня getter'а нет, а Singleton#init должен быть вызван всего один раз.
Для доступа к instance достаточно написать:
Singleton.instance.doSomething();

Конечно можно написать и getter, но на производительность это влиять не будет:
public enum Singleton {
    instance;

    private volatile boolean initialized = false;

    public synchronized void init(Parameter p1, Parameter p2) throw SomeException {
        if ( initialized ) {
            // do init work (some exceptions may occurr)
            initialized = true;
        }
    }

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


Есть три плюса решения приведённого выше:
1. Простота
2. Отделение создания синглтона (будет 1 инстанц на приложение) от его инициализации
3. Из п. 2 => возможность нормальной обработки Exception'а выкинутого методом Singleton#init. Под нормальной обработкой понимается не просто его логирование, но и возможное завершение приложения, если это надо (А также другие действия зачастую недоступные в статическом блоке)
>за счет отсутствия модификатора volatile, резко снижающего производительность в случае частых обращений к геттеру.

Откуда вы это взяли?
Честно говря оба решения показались весьма сложными и не привносящими ничего нового по сравнению с рассмотренными в той статье. Возможно я не до конца понял некоторые цели, которые преследовались.

Что значат пункты «Сериализуемость изменений ссылки на объект-Singleton», «Создание объекта-Singleton вне конструктора» и «Несериализуемое получение ссылки на объект-Singleton, обеспечивающее лучшую производительность»?
«Сериализуемость изменений ссылки на объект-Singleton» — означает, что ссылка на объект должна быть инициализирована потокобезопасным способом исключительно в одном потоке. Это, как мне кажется, очевидно;

«Создание объекта-Singleton вне конструктора» — означает, что нам, по некоторым причинам, о которых упоминается в других статьях про синглетон, крайне не желательно инициализировать ссылку на объект внутри конструктора синглетона;

«Несериализуемое получение ссылки на объект-Singleton, обеспечивающее лучшую производительность» — тоже, как мне кажется, весьма очевидное определение, означающее, что для эффективности получения ссылки на объект-синглетон, доступ к ссылке не должен осуществляться в synchronyzed блоке и/или переменная instance не должна быть описана с применением модификатора volatile, т.к. оба этих «артефакта» сильно снижают производительность программы при получении ссылки на объект-синглетон. И при этом, ссылка должна быть уже инициализирована потокобезопасным способом;

Что именно было не понятно?
Ну так как бы:
public class Singleton {

    private static Singleton instance;

    static {
        System.out.print("Singleton class Initializator\r\n");
        try {
          instance = new Singleton();
        } catch (Exception e) {
            System.out.print("Exception occurred\r\n");
        }
    }

    private Singleton() {
        System.out.print("Singleton has been initialized\r\n");
    }

    public static Singleton getInstance() {
        return instance;
    }
}
Вижу. Согласен с тезисом в посте. Немного поэстетствую и замечу, что приватность конструктора также важна.
Sign up to leave a comment.

Articles