Фабрика поточно-безопасного синглтона в Java

Это образец базового шаблона, который я использовал для Factory, который возвращает потокобезопасный синглтон:

public class UserServiceFactory {

    private volatile static UserService userService;

    private UserServiceFactory() { }

    public static UserService getInstance() {
        if (userService == null) {
            synchronized(UserServiceImpl.class) {            
                if (userService == null) {
                    userService = new UserServiceImpl();
                }        
            }
        }

        return userService;
    }

}

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

Есть ли менее подробный и / или менее затратный способ достичь той же цели в 1.6+?


person Dave Maple    schedule 22.05.2011    source источник
comment
возможный дубликат Эффективный способ реализации одноэлементного шаблона в Java   -  person skaffman    schedule 22.05.2011
comment
С уважением, прошу не закрывать вопрос как дубликат вышеупомянутого. Я привел конкретный пример кода и спросил о возможных улучшениях на основе недавних и ближайших изменений в модели памяти Java. Если этот тип вопросов не разрешен на основании более общего предыдущего вопроса, тогда мы теряем некоторую ценность для конечных пользователей - нет?   -  person Dave Maple    schedule 22.05.2011
comment
См. Также stackoverflow.com/questions/157198/ и < a href = "http://stackoverflow.com/questions/1625118/java-double-checked-locking" title = "двойная проверка блокировки java"> stackoverflow.com/questions/1625118/java-double-checked-locking   -  person matt b    schedule 22.05.2011


Ответы (3)


Используйте идиому Initialization On Demand Holder, это проще и удобнее для чтения:

public class UserServiceFactory {

    private UserServiceFactory () {}

    private static class UserServiceHolder {
        private static final UserService INSTANCE = new UserService();
    }

    public static UserService getInstance() {
        return UserServiceHolder.INSTANCE;
    }

}

Однако я бы предпочел идиому Just Create One.


Обновление: как видно из истории вопросов, вы используете Java EE. Если ваш контейнер поддерживает это, вы также можете сделать его _2 _ EJB и используйте @EJB для его внедрения (хотя @Stateless предпочтительнее, поскольку @Singleton по умолчанию заблокирован для чтения ).

@Singleton
public class UserService {}

например, с в управляемом компоненте JSF

@EJB
private UserService userService;

Таким образом вы делегируете задание по созданию экземпляра контейнеру.

person BalusC    schedule 22.05.2011
comment
Нет, начиная с Java 1.5. Прочтите об изменениях в volatile, которые заставляют его работать. - person Voo; 22.05.2011
comment
@Voo: Я понял это и удалил фразу. - person BalusC; 22.05.2011
comment
Да, нестабильное изменение давно назрело. Сделал многопоточное программирование, по крайней мере, немного проще. В любом случае +1 для разумного использования внутреннего класса - позволить виртуальной машине выполнять сложную работу за кулисами - это всегда хорошая идея;) - person Voo; 22.05.2011
comment
Этот пример очень похож на оптимизацию, которую я себе представлял. INSTANCE объявлен static final, поэтому, если UserService требует много времени для создания с помощью thread1, тогда thread2 ждет, я полагаю? - person Dave Maple; 22.05.2011
comment
@Dave: Да, загрузчик классов гарантирует безопасность потоков в static полях. Он вызывается только один раз. - person BalusC; 22.05.2011
comment
@BalusC - Обожаю, это именно то, что я искал. Большое Вам спасибо. Также понравилось читать на идиоме JustCreateOne. Определенно пища для размышлений. - person Dave Maple; 22.05.2011

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

Если вы хотите инициализировать экземпляр лениво и в основном без блокировки, тогда нет, вам нужно сделать это таким образом и убедиться, что вы используете Java> = 1.5.

Изменить: см. Решение BalusC, которое использует загрузчик классов более разумно. Обратите внимание, что все это работает, потому что загрузчик классов лениво инициализирует классы - то есть они загружаются только при первом доступе - и потому, что внутренние классы обрабатываются так же, как обычные классы в этом отношении (только потому, что вы загружаете внешний класс, не означает, что внутренний класс загружен)

person Voo    schedule 22.05.2011

Почему не просто

public synchronized static UserService getInstance() {
    if (userService == null) {
        userService = new UserServiceImpl();    
    }
    return userService;
}
person Ferguzz    schedule 22.05.2011
comment
Потому что вы всегда получаете накладные расходы на блокировку, хотя они вам нужны только при первом вызове функции. - person Voo; 22.05.2011