Дважды проверил блокировку Артикул - PullRequest
11 голосов
/ 01 октября 2008

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

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

15 голосов
/ 01 октября 2008

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

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

13 голосов
/ 01 октября 2008

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

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

public static final Singleton instance = new Singleton();

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

6 голосов
/ 01 октября 2008

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

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

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

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

3 голосов
/ 01 октября 2008

Я расскажу об этом здесь:

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

1 голос
/ 01 октября 2008

После Джон Скит Рекомендация:

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

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

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

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

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

0 голосов
/ 25 января 2010

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

0 голосов
/ 27 декабря 2008

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

0 голосов
/ 01 октября 2008

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

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

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

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

0 голосов
/ 01 октября 2008

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

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

0 голосов
/ 01 октября 2008

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

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

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

...