Безопасность потоков в Синглтоне

Я понимаю, что двойная блокировка в Java нарушена, поэтому каковы наилучшие способы сделать Singletons Thread Safe в Java? Первое, что приходит мне в голову:

class Singleton{
    private static Singleton instance;

    private Singleton(){}

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

Это работает? если это так, это лучший способ (я думаю, это зависит от обстоятельств, поэтому, если лучше указать конкретный метод, было бы полезно)

Ответы

Ответ 1

Джош Блох рекомендует использовать одноэлементный тип enum для реализации синглтонов (см. Эффективное Java 2nd Edition, пункт 3: Принудительное использование свойства singleton с помощью частного конструктора или типа перечисления).

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

Следующий пример берется прямо из книги.

public enum Elvis {
   INSTANCE;

   public void leaveTheBuilding() { ... }
}

Вот его заключительные аргументы:

Этот подход [...] более лаконичен, предоставляет бесплатную сериализующую технику и предоставляет отличную гарантию против множества экземпляров, даже в условиях сложных атак сериализации или отражения. Хотя этот подход еще не принят, одноэлементный тип перечисления является наилучшим способом реализации singleton.


Вкл enum постоянная однопользовательская гарантия

JLS 8.9. Перечисления

Тип перечисления не имеет экземпляров, отличных от тех, которые определены его константами перечисления. Это ошибка времени компиляции, чтобы попытаться явно создать экземпляр типа перечисления (§15.9.1).

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


При ленивой инициализации

Следующий фрагмент:

public class LazyElvis {
    enum Elvis {
        THE_ONE;
        Elvis() {
            System.out.println("I'M STILL ALIVE!!!");
        }       
    }
    public static void main(String[] args) {
        System.out.println("La-dee-daaa...");
        System.out.println(Elvis.THE_ONE);
    }
}

Производит следующий вывод:

La-dee-daaa...
I'M STILL ALIVE!!!
THE_ONE

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

Ответ 2

Я не вижу проблем с вашей реализацией (кроме того, что блокировка для singleton-monitor может использоваться другими методами по другим причинам и, следовательно, без необходимости предотвращать получение какого-либо другого потока от экземпляра). Этого можно избежать, добавив дополнительный Object lock для блокировки.

Эта статья в Википедии предлагает другой метод:

public class Something {
    private Something() {
    }

    private static class LazyHolder {
        private static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }
}

Из статьи:

Эта реализация является хорошо выполняющейся и параллельной реализацией, действующей во всех версиях Java.
...
Реализация основывается на хорошо заданной фазе инициализации исполнения в виртуальной машине Java (JVM); подробнее см. раздел 12.4 Спецификации языка Java (JLS).

Ответ 3

Мое предпочтение заключается в следующем:

class Singleton {
    private static final INSTANCE = new Singleton();

    private Singleton(){}

    public Singleton instance(){
        return INSTANCE;
    }
 }

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

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

Ответ 4

Джош Блох рекомендует 2 решения:

1) хуже:

class Singleton {

   public static Singleton instance = new Singleton();

   ...
}

2) лучше:

public enum Singleton {

   INSTANCE;

   ...
}

Ответ 5

Вы можете использовать этот фрагмент кода из wiki

public class Singleton {
   // Private constructor prevents instantiation from other classes
   private Singleton() {}

   /**
    * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
    * or the first access to SingletonHolder.INSTANCE, not before.
    */
   private static class SingletonHolder { 
     private static final Singleton INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
     return SingletonHolder.INSTANCE;
   }
 }