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

Блокировка с двойной проверкой в ​​.NET.

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

4b9b3361

Ответ 1

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

Это говорит:

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

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

Ответ 2

Двойная проверка блокировки теперь работает как на Java, так и на С# (модель памяти Java изменилась, и это один из эффектов). Однако вы должны понять это правильно. Если вы немного портите вещи, вы можете полностью потерять безопасность потока.

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

Ответ 3

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

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

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

Ответ 4

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

private static Singleton instance = new Singleton();

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

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(), но это в основном та же идея. Если существует возможная несогласованность при назначении поля singleton, то почему бы просто не использовать сначала локальную область действия, а затем назначить одноэлементное поле в последний момент до выхода из критического раздела? Я что-то пропустил?

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

Ответ 8

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

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