Когда я должен блокировать статический экземпляр в многопоточных синглетонах при использовании сеттера? - PullRequest
1 голос
/ 27 марта 2019

Попытка понять, когда рекомендуется блокировать статическую переменную или нет. Является ли статический инстанс-установщик поточно-ориентированным? Если нет, то должно ли это быть и почему (что является следствием того, что он не является потокобезопасным)?

class MyClass
{
    private static MyClass _instance;

    private static readonly object _padlock = new object();

    public static MyClass Instance
    {
        get
        {
            if(_instance == null)
            {
                lock(_padlock)
                {
                    if(_instance == null)
                    {
                        _instance = new MyClass();
                    }
                }
            }
            return _instance;
        }
        set => _instance = value;
    }

}

Ответы [ 2 ]

3 голосов
/ 27 марта 2019

Это называется Двойная проверка блокировки .

Однако Двойная проверка блокировки требует, чтобы базовое поле было volatile 1 .

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

Есть несколько способов сделать код потокобезопасным:

  • Избегайте двойной проверки блокировки и просто выполняйте все в операторе lock.
  • Сделать поле volatile , используя ключевое слово volatile.
  • Используйте класс Lazy, который гарантированно поточно-ориентированный .

Примечание : Полностью неохраняемый сеттер добавляет усложнение 3 ..

Однако, в вашем случае, использование двойной проверки блокировки , вероятно, будет хорошо работать с одной проверкой и блокировкой с полем volatile, но я думаю, что вам лучше всего просто набрать lock все и будь в безопасности

public static MyClass Instance
{
    get
    {
         lock(_padlock)
         {
             if(_instance == null)
                 _instance = new MyClass();
             return _instance;
         }

    }
    set 
    {
         lock(_padlock)
         {
             _instance = value;
         }
    } 
}

Примечание : Да, это повлечет за собой снижение производительности


Ссылка


Дополнительные ресурсы

1 голос
/ 27 марта 2019

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

  1. У вас есть блокировка на установщике, но вызов получателю поступает как раз перед тем, как блокировка включена. Вызывающий получает старый экземпляр.
  2. У вас есть блокировка на установщике, но вызов получателю поступает сразу после того, как блокировка включена. Вызывающая сторона ожидает освобождения блокировки, а затем получает новый экземпляр.
  3. У вас нет блокировки на сеттер, и вызов поступает только за до того, как вы замените экземпляр. Вызывающий получает старый экземпляр.
  4. У вас нет блокировки на сеттер, и вызов приходит только через после , когда вы заменяете экземпляр. Вызывающая сторона получает новый экземпляр.

С блокировками и без блокировок зависит, какой момент времени получатель получает.

Единственная проблема, которую я вижу, это если вы хотите установить Instance в null. Если это так, ваш текущий код не будет работать, потому что _instance может быть изменено между оператором if и его возвратом. Вы можете решить эту проблему, взяв копию ссылки:

public static MyClass Instance
{
    get
    {
        var instanceSafeRef = _instance;
        if(instanceSafeRef == null)
        {
            lock(_padlock)
            {
                if(_instance == null)
                {
                    _instance = new MyClass();
                }
                instanceSafeRef = _instance;
            }
        }
        return instanceSafeRef;
    }
    set => _instance = value;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...