Подтвердить что ты не робот

Синглтон и многопоточность в Java

Каков предпочтительный способ работы с классом Singleton в многопоточной среде?

Предположим, что у меня есть 3 потока, и все они пытаются получить доступ к методу getInstance() для одноэлементного класса в то же время -

  • Что произойдет, если синхронизация не будет сохранена?
  • Хорошо ли использовать метод synchronized getInstance() или использовать блок synchronized внутри getInstance().

Пожалуйста, сообщите, есть ли другой выход.

4b9b3361

Ответ 1

Задача является нетривиальной в теории, учитывая, что вы хотите сделать ее поистине безопасной.

Очень хорошая статья по этому вопросу найдена @IBM

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

Тогда вам, возможно, потребуется учесть, как компиляторы JIT обрабатывают записи вне порядка. Этот код будет несколько близок к решению, хотя в любом случае он не будет на 100% потоковым.

public static Singleton getInstance() {
    if (instance == null) {
        synchronized(Singleton.class) {      
            Singleton inst = instance;         
            if (inst == null) {
                synchronized(Singleton.class) {  
                    instance = new Singleton();               
                }
            }
        }
    }
    return instance;
}

Итак, вы должны, возможно, прибегнуть к чему-то менее ленивому:

class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() { }

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

Или, немного более раздутый, но более гибким способом является избежать использования статических синглетонов и использовать инфраструктуру внедрения, такую ​​как Spring для управления созданием объектов "singleton-ish" (и вы можете настроить ленивую инициализацию).

Ответ 2

Если вы говорите threadsafe, ленивую инициализацию singleton, вот классный код для использования, который выполняет 100% threadafe ленивую инициализацию без кода синхронизации:

public class MySingleton {

     private static class MyWrapper {
         static MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return MyWrapper.INSTANCE;
     }
}

Это создаст экземпляр singleton только при вызове getInstance(), и это будет 100% потокобезопасным! Это классика.

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

В стороне, я с нетерпением жду того дня, когда существует аннотация @Singleton, которая обрабатывает такие проблемы.

Отредактировано:

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

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

Если у вас есть только простой вызов метода getInstance(), то почти нет разницы - разница в том, что вся другая инициализация sttic завершилась бы до того, как экземпляр будет создан при использовании завернутой версии, но это легко с которым связано просто статическая переменная экземпляра, указанная последним в источнике.

Однако, если вы загружаете класс по имени, история совсем другая. Вызов Class.forName(className) для статической инициализации класса cuasing, чтобы произойти, поэтому, если используемый одноэлементный класс является свойством вашего сервера, с простой версией статический экземпляр будет создан при вызове Class.forName(), а не когда getInstance() называется. Я признаю, что это немного надуманно, так как вам нужно использовать отражение, чтобы получить экземпляр, но тем не менее здесь есть какой-то полный рабочий код, демонстрирующий мое утверждение (каждый из следующих классов - класс верхнего уровня):

public abstract class BaseSingleton {
    private long createdAt = System.currentTimeMillis();

    public String toString() {
        return getClass().getSimpleName() + " was created " + (System.currentTimeMillis() - createdAt) + " ms ago";
    }
}

public class EagerSingleton extends BaseSingleton {

    private static final EagerSingleton INSTANCE = new EagerSingleton();

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

public class LazySingleton extends BaseSingleton {
    private static class Loader {
        static final LazySingleton INSTANCE = new LazySingleton();
    }

    public static LazySingleton getInstance() {
        return Loader.INSTANCE;
    }
}

И главное:

public static void main(String[] args) throws Exception {
    // Load the class - assume the name comes from a system property etc
    Class<? extends BaseSingleton> lazyClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.LazySingleton");
    Class<? extends BaseSingleton> eagerClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.EagerSingleton");

    Thread.sleep(1000); // Introduce some delay between loading class and calling getInstance()

    // Invoke the getInstace method on the class
    BaseSingleton lazySingleton = (BaseSingleton) lazyClazz.getMethod("getInstance").invoke(lazyClazz);
    BaseSingleton eagerSingleton = (BaseSingleton) eagerClazz.getMethod("getInstance").invoke(eagerClazz);

    System.out.println(lazySingleton);
    System.out.println(eagerSingleton);
}

Вывод:

LazySingleton was created 0 ms ago
EagerSingleton was created 1001 ms ago

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

Ответ 3

Вам нужна синхронизация внутри getInstance, только если вы инициализируете свой синглтон лениво. Если вы можете создать экземпляр перед запуском потоков, вы можете отключить синхронизацию в получателе, потому что ссылка становится неизменной. Конечно, если сам объект singleton изменен, вам нужно будет синхронизировать его методы, которые получают доступ к информации, которая может быть изменена одновременно.

Ответ 4

Этот вопрос действительно зависит от того, как и когда создается ваш экземпляр. Если ваш метод getInstance лениво инициализирует:

if(instance == null){
  instance = new Instance();
}

return instance

Затем вы должны синхронизировать или вы можете получить несколько экземпляров. Эта проблема обычно рассматривается в разговорах на Double Checked Locking.

В противном случае, если вы создадите статический экземпляр вверх

private static Instance INSTANCE = new Instance();

тогда никакая синхронизация метода getInstance() не требуется.

Ответ 5

Никто не использует Enums, как предлагается в Эффективной Java?

Ответ 6

Лучший способ, как описано в эффективной java:

public class Singelton {

    private static final Singelton singleObject = new Singelton();

    public Singelton getInstance(){
        return singleObject;
    }
}

Нет необходимости в синхронизации.

Ответ 7

Если вы уверены, что ваша среда исполнения java , используя новую модель JMM (модель памяти Java, возможно, более новую, чем 5.0), двойная блокировка проверки просто прекрасна, но добавляет волатильность перед экземпляром. В противном случае вам лучше использовать статический внутренний класс, как сказал Богеман, или Enum в "Эффективной Java", как сказал Флориан Салихович.

Ответ 8

Для простоты, я думаю, что использование класса enum - лучший способ. Нам не нужна синхронизация. Java by construct всегда гарантирует, что создается только одна константа, независимо от того, сколько потоков пытается получить к ней доступ.

FYI. В некоторых случаях вам нужно поменять местами синглтон с другой реализацией. Затем нам нужно изменить класс, что является нарушением принципа Open Close. Проблема с singleton is, вы не можете расширить класс из-за наличия частного конструктора. Итак, это лучшая практика, с которой клиент разговаривает через интерфейс.

Реализация Singleton с классом enum и интерфейсом:

Client.java

public class Client{
    public static void main(String args[]){
        SingletonIface instance = EnumSingleton.INSTANCE;
        instance.operationOnInstance("1");
    }
}

SingletonIface.java

public interface SingletonIface {
    public void operationOnInstance(String newState);
}

EnumSingleton.java

public enum EnumSingleton implements SingletonIface{
INSTANCE;

@Override
public void operationOnInstance(String newState) {
    System.out.println("I am Enum based Singleton");
    }
}