Может ли этот Java-синглтон многократно перестраиваться в WebSphere 6? - PullRequest
13 голосов
/ 22 октября 2008

Я пытаюсь отследить проблему в нашей системе, и следующий код беспокоит меня. В нашем методе doPost () основного сервлета происходит следующее (имена были изменены, чтобы защитить виновных):

...
if(Single.getInstance().firstTime()){
   doPreperations();
}
normalResponse();
...

Синглтон 'Single' выглядит так:

private static Single theInstance = new Single();

private Single() {
...load properties...
}

public static Single getInstance() {
    return theInstance;
}

При установленном способе использования статического инициализатора вместо проверки на нулевое значение theInstance в методе getInstance () может ли это быть перестроено снова и снова?

PS - мы запускаем WebSphere 6 с приложением на Java 1.4

Ответы [ 10 ]

16 голосов
/ 22 октября 2008

Я нашел это на сайте Sun:

Несколько синглетов, одновременно загружаемых различными загрузчиками классов

Когда два загрузчика классов загружают класс, на самом деле у вас есть две копии класс, и каждый может иметь свой Единственный экземпляр. То есть особенно актуально в сервлетах работает в определенных двигателях сервлетов (iPlanet например), где каждый сервлет по умолчанию использует свой собственный класс погрузчик. Два разных сервлета доступ к совместному синглтону, в Факт, получить два разных объекта.

Несколько загрузчиков классов встречаются чаще обычно, чем вы думаете. когда браузеры загружают классы из сети для использования апплетами, они используют отдельный загрузчик классов для каждого сервера адрес. Точно так же Джини и РМИ системы могут использовать отдельный класс загрузчик для разных кодовых баз из которого они скачивают файлы классов. Если ваша собственная система использует пользовательский класс загрузчики, все те же вопросы могут возникает.

Если загружены разными загрузчиками классов, два класса с одинаковым именем, даже то же имя пакета, рассматриваются как отличается - даже если, на самом деле, они побайтовый тот же класс. загрузчики разных классов представляют различные пространства имен, которые различают классы (хотя классы имена совпадают), так что два MySingleton классы на самом деле различны. (Смотрите "Загрузчики классов как Механизм пространства имен »в разделе Ресурсы.) Поскольку два объекта Singleton принадлежат два класса с тем же именем, это будет с первого взгляда два объекта Singleton одного и того же класс.

Цитирование .

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

13 голосов
/ 22 октября 2008

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

Единственное исключение - если у вас несколько загрузчиков классов.

(из GeekAndPoke ):

alt text

5 голосов
/ 23 октября 2008

Нет, он не будет создавать несколько копий «Single». (Проблема Classloader будет посещена позже)

Реализация, которую вы описали, описана как 'Стремительная инициализация' в книге Брайанта Гетца - ' Параллелизм Java на практике '.

public class Single
{
    private static Single theInstance = new Single();

    private Single() 
    { 
        // load properties
    }

    public static Single getInstance() 
    {
        return theInstance;
    }
}

Однако код вам не нужен. Ваш код пытается выполнить отложенную инициализацию после создания экземпляра. Это требует от всей клиентской библиотеки выполнения firstTime () / doPreperation () 'перед ее использованием. Вы будете полагаться на то, что клиент поступит правильно, что сделает код очень хрупким.

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

public class Single
{
    private static Single theInstance = new Single();

    private Single() 
    { 
        // load properties
    }

    public static Single getInstance() 
    {   
        // check for initialization of theInstance
        if ( theInstance.firstTime() )
           theInstance.doPreparation();

        return theInstance;
    }
}

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

Есть много статей, написанных об инициализации Singleton, особенно о модели памяти. JSR 133 устраняет многие недостатки в модели памяти Java в Java 1.5 и 1.6.

В Java 1.5 и 1.6 у вас есть несколько вариантов, и они упоминаются в книге Джошуа Блоха « Effective Java ».

  1. Стремительная инициализация, как указано выше [EJ Item 3]
  2. Идиома класса «Ленивый держатель инициализации» [EJ Item 71]
  3. Тип Enum [EJ Item 3]
  4. Блокировка с двойной проверкой с помощью «изменчивого» статического поля [EJ Item 71]

Решения 3 и 4 будут работать только в Java 1.5 и выше. Так что лучшим решением было бы # 2.

Вот псевдо-реализация.

public class Single
{
    private static class SingleHolder
    {
        public static Single theInstance = new Single();
    }

    private Single() 
    { 
        // load properties
        doPreparation();
    }

    public static Single getInstance() 
    {
        return SingleHolder.theInstance;
    }
}

Обратите внимание, что doPreperation () находится внутри конструктора, поэтому вы гарантированно получите правильно инициализированный экземпляр. Кроме того, вы возвращаетесь к отложенной загрузке классов JVM и не нуждаетесь в какой-либо синхронизации getInstance ().

Одна вещь, которую вы заметили, что статическое поле theInstance не 'final'. В примере по параллелизму Java нет 'final', но EJ имеет. Возможно, Джеймс может добавить больше цвета к своему ответу о «загрузчике классов» и требовании «окончательного», чтобы гарантировать правильность,

Сказав это, есть побочный эффект от использования 'static final'. Компилятор Java очень агрессивен, когда видит статический финал и старается как можно больше встроить его. Это упоминается в блоге Джереми Мэнсона .

Вот простой пример.

файл: A.java

public class A
{
    final static String word = "Hello World";
}

файл: B.java

public class B
{
    public static void main(String[] args) {
        System.out.println(A.word);
    }
}

После того, как вы скомпилировали A.java и B.java, вы измените A.java на следующий.

файл: A.java

public class A
{
    final static String word = "Goodbye World";
}

Вы перекомпилируете 'A.java' и перезапустите B.class. Выход вы получите

Hello World

Что касается вопроса загрузчика классов, ответ - да, у вас может быть несколько экземпляров Singleton в нескольких загрузчиках классов. Вы можете найти больше информации в wikipedia . Также есть специальная статья на Websphere .

5 голосов
/ 22 октября 2008

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

При этом JVM не гарантирует, что ваш класс будет загружен только один раз. Однако даже если он загружен более одного раза, ваше веб-приложение все равно будет видеть только соответствующий синглтон, поскольку он будет загружен либо в загрузчик классов веб-приложения, либо в его родительский элемент. Если у вас развернуто несколько веб-приложений, то firstTime () будет вызываться один раз для каждого приложения.

Наиболее очевидные вещи, которые нужно проверить, это то, что firstTime () необходимо синхронизировать и установить флаг firstTime перед выходом из этого метода.

5 голосов
/ 22 октября 2008

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

Одна вещь, на которую я бы обратил внимание, - это метод firstTime() - почему работа в doPreparations() не может быть обработана в самом синглтоне?

Звучит как неприятный набор зависимостей.

2 голосов
/ 22 октября 2008

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

Конечно, если он загружается из отдельных загрузчиков классов, вы получаете несколько «синглетонов», но это ограничение каждой одноэлементной идиомы в Java.

EDIT : Биты firstTime () и doPreparations () выглядят подозрительно. Разве они не могут быть перемещены в конструктор экземпляра синглтона?

1 голос
/ 22 октября 2008

Теоретически он будет построен только один раз. Однако этот шаблон ломается на различных серверах приложений, где вы можете получить несколько экземпляров классов 'singleton' (поскольку они не являются поточно-ориентированными).

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

1 голос
/ 22 октября 2008

Нет - статическая инициализация instance будет выполняться только один раз. Две вещи для рассмотрения:

  • Это не поточно-ориентированный (экземпляр не "опубликован" в основной памяти)
  • Ваш firstTime метод, вероятно, вызывается несколько раз, если не будет правильно синхронизирован
0 голосов
/ 13 июня 2009

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

В приведенном ниже коде вы можете увидеть, как он создается только один раз (при первом вызове конструктора)

дата пакета;

import java.util.Date;

открытый класс USDateFactory реализует DateFactory { приватная статическая USDateFactory usdatefactory = null;

private USDateFactory () { }

public static USDateFactory getUsdatefactory() {
    if(usdatefactory==null) {
        usdatefactory = new USDateFactory();
    }
    return usdatefactory;
}

public String getTextDate (Date date) {
    return null;
}

public NumericalDate getNumericalDate (Date date) {
    return null;
}

}

0 голосов
/ 22 октября 2008

Это будет загружено только один раз, когда класс загружается загрузчиком классов. В этом примере представлена ​​лучшая реализация Singleton, она максимально загружена и поточно-ориентирована. Более того, он работает во всех известных версиях Java. Это решение является наиболее переносимым среди различных компиляторов Java и виртуальных машин.


public class Single {

private static class SingleHolder {
   private static final Single INSTANCE = new Single();
}

private Single() {
...load properties...
}

public static Single getInstance() {
    return SingleHolder.INSTANCE;
}

}

Внутренний класс ссылается не ранее (и поэтому загружается не ранее загрузчиком классов), чем в момент вызова getInstance (). Таким образом, это решение является поточно-ориентированным, не требуя специальных языковых конструкций (то есть volatile и / или синхронизированных).

...