Дважды проверил блокировку Артикул

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

Листинг 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;
}

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

Спасибо заранее.

10 ответов

Решение

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

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

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

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

public static final Singleton instance = new Singleton();

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

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

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

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;
    }

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

Я покрываю кучу этого здесь:

http://tech.puredanger.com/2007/06/15/double-checked-locking/

Следуя рекомендации Jon Skeet:

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

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

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

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

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

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

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

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

Иначе я не вижу никакого смысла.

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

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 или ниже, так или иначе.

Начиная с Java 5, вы можете сделать двойную проверку блокировки, объявив поле volatile.

См. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html для полного объяснения.

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

Код в листинге 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" не достигнута?

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

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

Другие вопросы по тегам