Потоки: Ленивая Инициализация против Статической Ленивой Инициализации - PullRequest
10 голосов
/ 14 сентября 2011

Я просматриваю видеопрезентацию модели памяти Java, и автор говорит, что лучше использовать Static Lazy Initialization по сравнению с Lazy Initialization, и я не совсем понимаю, что он хочет сказать.

Я хотел бы связаться с сообществом и был бы признателен, если бы кто-нибудь смог объяснить разницу между Static Lazy Initialization и Lazy Initialization простым примером кода Java.

Справочник: Расширенные темы программирования - Модель памяти Java

Ответы [ 5 ]

18 голосов
/ 14 сентября 2011

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

Инициализация класса по своей сути поточно-ориентирована, и, если вы можете инициализировать объект при инициализации класса, создание объекта тоже поточно-ориентировано.

Вот пример поточно-ориентированного статически инициализированного объекта

public class MySingletonClass{

   private MySingletonClass(){

   }
   public static MySingletonClass getInstance(){
         return IntiailizationOnDemandClassholder.instance;
   }

   private static class IntiailizationOnDemandClassHolder{
         private static final MySingletonClass instance = new MySingletonClass();

   }

}

Что важно знать здесь, переменная экземпляра MySingletonClass никогда не будет создана и / или инициализирована до тех пор, пока не будет вызван getInstance(). И снова, поскольку инициализация класса является поточно-ориентированной, переменная instance, равная IntiailizationOnDemandClassholder, будет загружена безопасно, один раз и видима для всех потоков.

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

public class DCLLazySingleton{
  private static volatile DCLLazySingleton instance;

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

и

public class ThreadSafeLazySingleton{
   private static ThreadSafeLazySingleton instance;

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

}

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

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

1 голос
/ 15 сентября 2011

Стоит отметить, что простейшей поточно-безопасной статической ленивой инициализацией является использование enum Это работает, потому что инициализация статических полей является поточно-ориентированной, и классы все равно лениво загружаются.

enum ThreadSafeLazyLoadedSingleton {
    INSTANCE;
}

Класс, который использует ленивое загруженное значение, является String. HashCode вычисляется только при первом использовании. После этого используется кэшированный hashCode.

Я не думаю, что вы можете сказать, что одно лучше другого, потому что они на самом деле не взаимозаменяемы.

1 голос
/ 14 сентября 2011

Я думаю, что автор в презентации ссылается на тот факт, что статическое поле будет инициализировано только один раз потокобезопасным способом при первом использовании класса, который содержит это поле (это гарантируется JMM):

class StaticLazyExample1 {

   static Helper helper = new Helper();

   static Helper getHelper() {
      return helper;
   }
}

Здесь helper поле будет инициализировано при первом использовании класса StaticLazyExample1 (т.е. при вызове конструктора или статического метода)

Существует также идиома «Инициализация по требованию», которая основана напри статической отложенной инициализации:

class StaticLazyExample2 {

  private static class LazyHolder {
    public static Helper instance = new Helper();
  }

  public static Helper getHelper() {
    return LazyHolder.instance;
  }
}

Здесь экземпляр Helper будет создан только при первом вызове StaticLazyExample2.getHelper() статического метода.Этот код гарантированно является потокобезопасным и правильным из-за гарантий инициализации для статических полей;если поле установлено в статическом инициализаторе, оно гарантированно будет правильно отображаться для любого потока, который обращается к этому классу.

UPDATE

В чем разница между обоими типами инициализации?

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

class LazyInitExample1 {

  private Helper instance;

  public synchronized Helper getHelper() {
    if (instance == null) instance == new Helper();
    return instance;
  }
}

или использовать двойную проверку блокировки:

class LazyInitExample2 {

    private volatile Helper helper;

    public Helper getHelper() {
      if (helper == null) {
          synchronized (this) {
              if (helper == null) helper = new Helper();
          }
      }
      return helper;
    }
}

Должен ли я упомянуть, что они оба требуют явной синхронизации и несут дополнительные временные издержки по сравнению со статической отложенной инициализацией?

1 голос
/ 14 сентября 2011

Ссылка здесь была бы хороша, конечно. У них обоих одна и та же основная идея: зачем выделять ресурсы (память, процессор), если это не нужно? Вместо этого отложите распределение этих ресурсов до тех пор, пока они действительно не понадобятся. Это может быть хорошо в интенсивной среде, чтобы избежать потерь, но может быть очень плохо, если вам нужны результаты прямо сейчас и вы не можете ждать. Добавить «ленивую, но осторожную» систему очень сложно (та, которая обнаруживает простои и выполняет эти ленивые вычисления, когда у нее появляется свободное время.)

Вот пример ленивой инициализации.

class Lazy {

    String value;
    int computed;

    Lazy(String s) { this.value = s; }

    int compute() {
        if(computed == 0) computed = value.length();
        return computed;
    }

}

Вот статическая ленивая инициализация

class StaticLazy {

    private StaticLazy staticLazy;
    static StaticLazy getInstance() {
        if(staticLazy == null) staticLazy = new StaticLazy();
        return staticLazy;
    }
}
0 голосов
/ 15 сентября 2011

Различие заключается в механизме реализации ленивой инициализации. Под Static Lazy Initialization я полагаю, что презентатор означает это решение , основанное на совместимости JVM с любой версией Java ( см. 12.4 Инициализация классов и интерфейсов спецификации языка Java ) .

Lazy Initialization вероятно означает ленивую инициализацию, описанную во многих других ответах на этот вопрос. Такие механизмы инициализации делают предположения о JVM, которые не являются поточно-ориентированными до Java 5 (поскольку Java 5 имеет спецификацию реальной модели памяти).

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