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

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

Я читал эту статью о "двойной проверке блокировки" и из основной темы статьи мне было интересно, почему на некоторых в статье автор использует следующий Идиом:

Листинг 7. Попытка решить проблему нестандартной записи

public static Singleton getInstance()  
{
    if (instance == null)
    {
        synchronized(Singleton.class) {      //1
            Singleton inst = instance;         //2
            if (inst == null)
            {
                synchronized(Singleton.class) {  //3
                    inst = new Singleton();        //4
                }
                instance = inst;                 //5
            }
        }
    }
    return instance;
}

И мой вопрос: Есть ли причина для синхронизации дважды кода с той же блокировкой? Есть ли в этом цель?

Большое спасибо заранее.

4b9b3361

Ответ 1

Точка блокировки дважды заключалась в попытке предотвратить запись вне порядка. Модель памяти указывает, где могут возникать переупорядочивания, частично в терминах блокировок. Блокировка гарантирует, что никакие записи (включая любые внутри конструктора singleton) не появятся после "instance = inst;" линия.

Однако, чтобы углубиться в предмет, я бы рекомендовал статью Билла Пьюга. И тогда никогда не пытайтесь это сделать:)

Ответ 2

В статье упоминается модель памяти Java до 5.0 (JMM). Под этой моделью, оставляя синхронизированный блок, принудительно выписывается в основную память. Похоже, что это попытка убедиться, что объект Singleton вытолкнут перед ссылкой на него. Однако это не совсем так, потому что запись в экземпляр может быть перенесена в блок - мотель roach.

Однако модель pre-5.0 никогда не была правильно реализована. 1.4 следует следовать модели 5.0. Классы инициализируются лениво, поэтому вы можете просто написать

public static final Singleton instance = new Singleton();

Или лучше, не используйте синглтоны, потому что они злы.

Ответ 3

Джон Скит прав: прочитайте статью Билла Пьюга. Идиома, которую использует Ганс, - это точная форма, которая не будет работать и не должна использоваться.

Это небезопасно:

private static Singleton instance;

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

Это также небезопасно:

public static Singleton getInstance()  
{
    if (instance == null)
    {
        synchronized(Singleton.class) {      //1
            Singleton inst = instance;         //2
            if (inst == null)
            {
                synchronized(Singleton.class) {  //3
                    inst = new Singleton();        //4
                }
                instance = inst;                 //5
            }
        }
    }
    return instance;
}

Не делай ни того, ни другого.

Вместо этого синхронизируйте весь метод:

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

Если вы не возвращаете этот объект за миллион раз в секунду, производительность в реальном выражении незначительна.

Ответ 5

Следуя John Skeet Рекомендация:

Однако, чтобы углубиться в тему Я бы рекомендовал статью Билла Пью. А также то никогда не пытайтесь это сделать:)

И вот ключ для второго блока синхронизации:

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

Итак, в основном с блоком внутренней синхронизации мы пытаемся "обмануть" JMM, создав экземпляр внутри блока синхронизации, чтобы заставить JMM выполнить это распределение до завершения блока синхронизации. Но проблема здесь в том, что JMM направляет нас и перемещает привязку, которая находится перед блоком синхронизации внутри блока синхронизации, переместив нашу проблему обратно в начало.

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

Ответ 6

Хорошо, но в статье сказано, что

Код в листинге 7 не работает из-за текущего определения модели памяти. Спецификация языка Java (JLS) требует, чтобы код в синхронизированном блоке не перемещался из синхронизированного блока. Тем не менее, он не говорит, что код не в синхронизированном блоке не может быть перемещен в синхронизированный блок.

И также похоже, что JVM делает следующий перевод в "псевдокод" в ASM:

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {      //1
      Singleton inst = instance;         //2
      if (inst == null)
      {
        synchronized(Singleton.class) {  //3
          //inst = new Singleton();      //4
          instance = new Singleton();               
        }
        //instance = inst;               //5
      }
    }
  }
  return instance;
}

До сих пор точка без записи после "instance = inst" не выполняется?

Теперь я прочитаю статью, спасибо за ссылку.

Ответ 8

Относительно этой идиомы есть очень целесообразная и уточняющая статья:

http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html?page=1

С другой стороны, я думаю, что, по мнению dhighwayman.myopenid, автор поставил один синхронизированный блок, относящийся к одному и тому же классу (synchronized (Singleton.class)) в другом синхронизированном блоке, относящемся к тому же классу. Это может произойти, когда в этом блоке создается новый экземпляр (Singleton inst = instance;) и гарантируется, что он должен быть потокобезопасным, необходимо записать еще одну синхронизированную.

В противном случае я не вижу смысла.

Ответ 9

См. раздел Google Tech Talk в Java Memory Model для действительно приятного ознакомления с точками JMM. Поскольку он отсутствует здесь, я также хотел бы указать на блог Джереми Мэнсона 'Java Concurrency' esp. сообщение Двойная проверка блокировки (у любого, кто есть что-то в мире Java, есть статья об этом:).

Ответ 10

Для Java 5 и выше на самом деле существует вариант с двойным контролем, который может быть лучше, чем синхронизация всего аксессора. Это также упоминается в Double-Checked Locking Declaration:

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

Основное различие заключается в использовании volatile в объявлении переменной - в противном случае это не сработает, и в любом случае оно не работает на Java 1.4 или меньше.