Java Singleton и синхронизация

Просьба уточнить мои запросы относительно Singleton и Multithreading:

  • Каков наилучший способ реализации Singleton в Java, в многопоточном окружающая среда?
  • Что происходит, когда несколько потоков пытаются получить доступ к getInstance() метод в то же время?
  • Можем ли мы сделать singleton getInstance() synchronized?
  • Необходима ли синхронизация при использовании классов Singleton?

Ответ 1

Да, это необходимо. Существует несколько методов, которые можно использовать для обеспечения безопасности потоков с ленивой инициализацией:

Драконовская синхронизация:

private static YourObject instance;

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

Это решение требует, чтобы каждый поток был синхронизирован, когда на самом деле должны быть только первые несколько.

Двойная проверка синхронизации:

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Это решение гарантирует, что только первые несколько потоков, которые пытаются приобрести ваш синглтон, должны пройти процесс приобретения блокировки.

Инициализация по запросу:

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

В этом решении используется модель памяти Java, гарантирующая инициализацию класса для обеспечения безопасности потоков. Каждый класс может быть загружен только один раз, и он будет загружен только тогда, когда это необходимо. Это означает, что при первом вызове getInstance будет загружен InstanceHolder и будет создан instance, а так как это управляется ClassLoader s, дополнительная синхронизация не требуется.

Ответ 2

Этот шаблон выполняет поточно-инициализацию потока без использования явной синхронизации!

public class MySingleton {

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

     private MySingleton () {}

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

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

Это похоже на волшебство.

Он действительно очень похож на шаблон перечисления Jhurtado, но я нахожу шаблон перечисления как злоупотребление концепцией enum (хотя он действительно работает)

Ответ 3

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

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

а затем просто ваши потоки будут использовать ваш экземпляр, например:

Singleton.SINGLE.myMethod();

Ответ 4

Да, вам нужно сделать синхронизацию getInstance(). Если это не так, может возникнуть ситуация, когда могут быть сделаны несколько экземпляров класса.

Рассмотрим случай, когда у вас есть два потока, которые вызывают getInstance() одновременно. Теперь представьте, что T1 выполняется только после проверки instance == null, а затем запускается T2. На данный момент экземпляр не создается или не устанавливается, поэтому T2 передаст проверку и создаст экземпляр. Теперь представьте, что выполнение переключается обратно на T1. Теперь синглтон создан, но T1 уже проверил! Он снова начнет делать объект! Выполнение синхронизации getInstance() предотвращает эту проблему.

Есть несколько способов сделать потоки в одноэлементных потоках, но синхронизация getInstance(), возможно, самая простая.

Ответ 5

Enum singleton

Простейшим способом реализации Singleton, который является потокобезопасным, является использование Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Этот код работает с момента появления Enum в Java 1.5

Двойная проверка блокировки

Если вы хотите закодировать "классический" синглтон, который работает в многопоточной среде (начиная с Java 1.5), вы должны использовать этот.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

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

Это не является потокобезопасным до 1,5, поскольку реализация ключевого слова volatile отличается.

Ранняя загрузка Singleton (работает даже до Java 1.5)

Эта реализация создает экземпляр singleton при загрузке класса и обеспечивает безопасность потоков.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}

Ответ 6

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

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

Ответ 7

Каков наилучший способ реализации Singleton в Java в многопоточной среде?

Обратитесь к этому сообщению для лучшего способа реализации Singleton.

Что такое эффективный способ реализации одноэлементного шаблона в Java?

Что происходит, когда несколько потоков пытаются получить доступ к методу getInstance() в то же время?

Это зависит от способа реализации метода. Если вы используете двойную блокировку без изменчивой переменной, вы можете получить частично сконструированный объект Singleton.

Обратитесь к этому вопросу для получения дополнительной информации:

Почему волатильность используется в этом примере двойной блокировки блокировки

Можно ли сделать singleton getInstance() синхронизированным?

Действительно ли нужна синхронизация при использовании классов Singleton?

Не требуется, если вы реализуете Singleton ниже способами

  • статичная интинализация
  • enum
  • LazyInitalaization with Initialization-on-demand_holder_idiom

Подробнее см. этот вопрос

Шаблон проектирования Java Singleton: вопросы

Ответ 8

public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Источник: Эффективная Java → Пункт 2

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