Дважды проверил блокировку в .NET - PullRequest
52 голосов
/ 27 декабря 2008

Я наткнулся на эту статью , в которой обсуждается, почему в Java нарушена парадигма блокировки двойной проверки Допустима ли эта парадигма для .NET (в частности, C #), если переменные объявлены volatile?

Ответы [ 8 ]

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

Двойная проверка блокировки теперь работает как в Java, так и в C # (модель памяти Java изменилась, и это один из эффектов). Тем не менее, вы должны получить точно правильно. Если вы все испортите, вы можете потерять безопасность потока.

Как уже говорилось в других ответах, если вы реализуете шаблон singleton , есть гораздо лучшие способы сделать это. Лично, если я нахожусь в ситуации, когда мне приходится выбирать между двойной проверкой блокировки и кодом «каждый раз блокировать», я буду использовать блокировку каждый раз, пока не получу реальные доказательства того, что это вызывает узкое место. Когда дело доходит до многопоточности, простой и заведомо правильный шаблон стоит многого.

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

Реализация шаблона Singleton в C # говорит об этой проблеме в третьей версии.

Там написано:

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

Автор, похоже, подразумевает, что двойная блокировка с меньшей вероятностью сработает, чем другие стратегии, и поэтому не должна использоваться.

24 голосов
/ 26 мая 2010

.NET 4.0 имеет новый тип: Lazy<T>, который устраняет любые опасения по поводу неправильной схемы. Это часть новой параллельной библиотеки задач.

См. Центр разработки параллельных вычислений MSDN: http://msdn.microsoft.com/en-us/concurrency/default.aspx

Кстати, есть обратный порт (я думаю, что он не поддерживается) для .NET 3.5 SP1 доступен здесь .

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

Обратите внимание, что в Java (и, скорее всего, также в .Net) двойная проверка блокировки для инициализации синглтона совершенно не нужна, а также не работает. Поскольку классы не инициализируются до тех пор, пока они не будут впервые использованы, этим достигается ленивая инициализация;

private static Singleton instance = new Singleton();

Если ваш класс Singleton не содержит таких вещей, как константы, к которым можно получить доступ перед первым использованием экземпляра Singleton, это все, что вам нужно сделать.

3 голосов
/ 13 апреля 2015

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

Если кто-то может сказать мне, страдает ли этот код проблемой, упомянутой в статье Кэмерона, пожалуйста, сделайте.

public sealed class Singleton {
    static Singleton instance = null;
    static readonly object padlock = new object();

    Singleton() {
    }

    public static Singleton Instance {
        get {
            if (instance != null) {
                return instance;
            }

            lock (padlock) {
                if (instance != null) {
                    return instance;
                }

                tempInstance = new Singleton();

                // initialize the object with data

                instance = tempInstance;
            }
            return instance;
        }
    }
}
2 голосов
/ 27 мая 2011

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

Синглтон, использующий логическое значение, не работает. Порядок операций в разных потоках не гарантируется, если вы не пройдете через барьер памяти. Другими словами, как видно из второго потока, created = true может быть выполнено до instance= new Singleton();

1 голос
/ 13 июня 2012

Я не совсем понимаю, почему существует множество шаблонов реализации для двойной проверки блокировки (по-видимому, для обхода идиосинкразий компилятора в разных языках). Статья в Википедии на эту тему показывает наивный метод и возможные пути решения проблемы, но ни один не так прост (как на C #):

public class Foo
{
  static Foo _singleton = null;
  static object _singletonLock = new object();

  public static Foo Singleton
  {
    get
    {
      if ( _singleton == null )
        lock ( _singletonLock )
          if ( _singleton == null )
          {
            Foo foo = new Foo();

            // Do possibly lengthy initialization,
            // but make sure the initialization
            // chain doesn't invoke Foo.Singleton.
            foo.Initialize();

            // _singleton remains null until
            // object construction is done.
            _singleton = foo;
          }
      return _singleton;
    }
  }

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

Есть аргумент @ michael-borgwardt, что в C # и Java статическое поле инициализируется только один раз при первом использовании, но , что поведение зависит от языка. И я часто использовал этот шаблон для ленивой инициализации свойства коллекции (например, user.Sessions).

0 голосов
/ 28 февраля 2011

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

private static Singleton instance;
private static boolean created;
public static Singleton getInstance() {
    if (!created) {
        synchronized (Singleton.class) {
            if (!created) {
                instance = new Singleton();
                created = true;
            }
        }
    }
    return instance;
}
...