Нет, он не будет создавать несколько копий «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 ».
- Стремительная инициализация, как указано выше [EJ Item 3]
- Идиома класса «Ленивый держатель инициализации» [EJ Item 71]
- Тип Enum [EJ Item 3]
- Блокировка с двойной проверкой с помощью «изменчивого» статического поля [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 .