Я пытаюсь отследить проблему в нашей системе, и меня беспокоит следующий код. Следующее происходит в нашем методе 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.




Нет, это не будет строиться снова и снова. Он статический, поэтому он будет создан только один раз, когда класс будет впервые затронут загрузчиком классов.
Единственное исключение - если у вас несколько загрузчиков классов.
(от GeekAndPoke):

Нет - статическая инициализация instance может быть выполнена только один раз. Следует учитывать две вещи:
firstTime, вероятно, вызывается несколько раз, если он не синхронизирован должным образом.Внутри JVM есть как «основная» память (совместно используемая между потоками), так и «локальная для потока» память. Поскольку поле экземпляра не является ни окончательным, ни изменчивым, изменения переменной, сделанные в одном потоке, не обязательно будут видны в другом. Другой способ обеспечить видимость - ключевое слово synchronized.
Я нашел это на сайте Sun:
Multiple Singletons Simultaneously Loaded by Different Class Loaders
When two class loaders load a class, you actually have two copies of the class, and each one can have its own Singleton instance. That is particularly relevant in servlets running in certain servlet engines (iPlanet for example), where each servlet by default uses its own class loader. Two different servlets accessing a joint Singleton will, in fact, get two different objects.
Multiple class loaders occur more commonly than you might think. When browsers load classes from the network for use by applets, they use a separate class loader for each server address. Similarly, Jini and RMI systems may use a separate class loader for the different code bases from which they download class files. If your own system uses custom class loaders, all the same issues may arise.
If loaded by different class loaders, two classes with the same name, even the same package name, are treated as distinct -- even if, in fact, they are byte-for-byte the same class. The different class loaders represent different namespaces that distinguish classes (even though the classes' names are the same), so that the two
MySingletonclasses are in fact distinct. (See "Class Loaders as a Namespace Mechanism" in Resources.) Since two Singleton objects belong to two classes of the same name, it will appear at first glance that there are two Singleton objects of the same class.
В дополнение к вышеупомянутой проблеме, если firstTime() не синхронизирован, у вас также могут быть проблемы с потоками.
Что повлияет на любую потенциальную реализацию.
Действительно ... это хороший ответ на вопрос, совершенно отличный от того, что было задано. Поэтому я не уверен, почему за него так проголосовали?
На самом деле, он спрашивает, возможно ли, чтобы его синглтон перестраивался снова и снова, и ответ положительный - в некоторых контейнерах, которые используют несколько загрузчиков классов, это возможно.
Проблемы с потоками: синхронизация firstTime () вообще не поможет; вы должны синхронизировать код, который защищает firstTime ()!
Это немного вводит в заблуждение, хотя являются во многих случаях, когда есть несколько загрузчиков классов, в большинстве случаев загрузчик классов не загружает класс, который уже был загружен родителем. Таким образом, хотя вы не должны полагаться на уникальность класса, этот эффект будет довольно редко побеждать синглтон. Гораздо более распространенным был бы случай кластера виртуальных машин, каждая из которых с радостью захватила бы свой собственный синглтон.
Теоретически он будет построен только один раз. Однако этот шаблон не работает на различных серверах приложений, где вы можете получить несколько экземпляров «одноэлементных» классов (поскольку они не являются потокобезопасными).
Кроме того, много критиковался шаблон singleton. См., Например, Синглтон, я люблю тебя, но ты меня подводишь
Синглтоны могут быть поточно-ориентированными. Пример в вопросе является поточно-ориентированным. Если вы получаете несколько экземпляров на сервере приложений, это, вероятно, связано с наличием нескольких загрузчиков классов.
Единственное, что я бы изменил в этой реализации синглтона (кроме отказа от синглтона вообще), - это сделать поле экземпляра окончательным. Статическое поле будет инициализировано один раз при загрузке класса. Поскольку классы загружаются лениво, вы фактически получаете ленивое создание экземпляров бесплатно.
Конечно, если он загружается из отдельных загрузчиков классов, вы получаете несколько «синглтонов», но это ограничение каждой идиомы синглтона в Java.
РЕДАКТИРОВАТЬ: биты firstTime () и doPreparations () действительно выглядят подозрительно. Нельзя ли их переместить в конструктор экземпляра синглтона?
Как уже упоминалось, статический инициализатор будет запускаться только один раз для загрузчика классов.
Одна вещь, на которую я хотел бы обратить внимание, - это метод firstTime() - почему работа в doPreparations() не может выполняться в самом синглтоне?
Похоже на неприятный набор зависимостей.
На самом деле, похоже, что кто-то не разбирается в разработке сервлетов.
Он будет загружен только один раз, когда класс будет загружен загрузчиком классов. Этот пример обеспечивает лучшую реализацию синглтона, однако он максимально ленивый и потокобезопасный. Более того, он работает во всех известных версиях 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 (). Таким образом, это решение является поточно-ориентированным и не требует специальных языковых конструкций (т. Е. Изменчивых и / или синхронизированных).
Известно, что ленивая загрузка не работает.
Можете ли вы предоставить источник, подтверждающий это? IODH использует отложенную инициализацию класса. JVM не будет выполнять статический инициализатор класса, пока вы не коснетесь чего-либо в классе. Это относится и к статическим вложенным классам в соответствии со спецификацией языка Java.
Здесь SingleHoler не инициализирует INSTANCE, пока кто-нибудь не вызовет getInstance (). И он потокобезопасен, поскольку инициализируется в статическом блоке.
@Мартин. Это не сломано, если реализовано правильно. Я бы использовал более простой подход, без держателя, но с этой реализацией все в порядке.
Код @Martin nkr1pt не использует блокировку с двойной проверкой. Вы говорите, что с этим кодом что-то не так? В своих комментариях вы объединяете блокировку с двойной проверкой и ленивую инициализацию. Последнее не подразумевает первого. DCL без volatile сломан. С летучими веществами это безопасно.
Нет абсолютно никакой разницы между использованием статического инициализатора и ленивой инициализацией. На самом деле гораздо проще испортить ленивую инициализацию, которая также обеспечивает синхронизацию. JVM гарантирует, что статический инициализатор всегда запускается до обращения к классу, и это произойдет один и только один раз.
Тем не менее, JVM не гарантирует, что ваш класс будет загружен только один раз. Однако даже если он загружается более одного раза, ваше веб-приложение по-прежнему будет видеть только соответствующий синглтон, поскольку он будет загружен либо в загрузчик классов веб-приложения, либо в его родительский элемент. Если у вас развернуто несколько веб-приложений, то firstTime () будет вызываться один раз для каждого приложения.
Наиболее очевидные вещи, которые нужно проверить, - это то, что firstTime () необходимо синхронизировать и что флаг firstTime устанавливается перед выходом из этого метода.
Известно, что отложенная инициализация может быть нарушена.
Несинхронизированный есть. Инициализация класса всегда синхронизирована, поэтому это не проблема. Ссылка, которую вы даете, противоречит вашим словам :)
Питер Хаггар: идиома проверенных блокировок все еще не работает в новой модели памяти. ibm.com/developerworks/java/library/j-dcl.html
Брайан Гетц: Согласно новой модели памяти, это «исправление» блокировки с двойной проверкой делает идиому поточно-ориентированной. ibm.com/developerworks/library/j-jtp03304
Не понимаю, какое отношение это имеет к проблеме. Инициализация в статическом инициализаторе совершенно потокобезопасна. JVM блокирует класс при инициализации, поэтому одновременный доступ должен подождать.
Нет, он не будет создавать несколько копий «Сингла». (Проблема с загрузчиком классов будет рассмотрена позже)
Реализация, которую вы обрисовали в общих чертах, описана в книге Брайанта Гетца как «Стремительная инициализация» - «Параллелизм Java на практике».
public class Single
{
private static Single theInstance = new Single();
private Single()
{
// load properties
}
public static Single getInstance()
{
return theInstance;
}
}
Однако код вам не нужен. Ваш код пытается выполнить отложенную инициализацию после создания экземпляра. Это требует, чтобы вся клиентская библиотека выполняла firstTime () / doPreparation () перед ее использованием. Вы будете полагаться на то, что клиент поступит правильно, что сделает код очень хрупким.
Вы можете изменить код следующим образом, чтобы не было дублирования кода.
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).
Об инициализации синглтона, особенно о модели памяти, написано много статей. JSR 133 устранил многие слабые места в модели памяти Java в Java 1.5 и 1.6.
В Java 1.5 и 1.6 у вас есть несколько вариантов, и они упомянуты в книге Джошуа Блоха «Эффективная Java».
Решение 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;
}
}
Обратите внимание, что doPreparation () находится внутри конструктора, поэтому вы гарантированно получите правильно инициализированный экземпляр. Кроме того, вы используете ленивую загрузку класса JVM и не нуждаетесь в какой-либо синхронизации getInstance ().
Вы заметили, что статическое поле theInstance - это не «окончательный».. В примере с Java Concurrency нет "final", а в EJ есть. Возможно, Джеймс может добавить больше цвета к своему ответу на «загрузчик классов» и требованию «final», чтобы гарантировать правильность,
Сказав это, есть побочный эффект, связанный с использованием 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 в нескольких загрузчиках классов. Вы можете найти больше информации на википедия. Также есть конкретная статья по Вебсфера.
Необязательно, чтобы единственный экземпляр был окончательным (это действительно не очень хорошая идея, потому что это не позволит вам переключить его поведение с использованием других шаблонов).
дата пакета;
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;
}
}
Что вы имеете в виду, говоря «Это не потокобезопасно (экземпляр не« публикуется »в основной памяти)»?