Почему этот одноэлементный код конструкции НЕ является потокобезопасным - PullRequest
5 голосов
/ 21 сентября 2010

Почему в этом коде написано "Не потокобезопасен"? Спасибо

class Singleton
{
  private static Singleton _instance;

  // Constructor is 'protected'
  protected Singleton()
  {
  }

  public static Singleton Instance()
  {
    // Uses lazy initialization.
    // **Note: this is not thread safe.**
    if (_instance == null)
    {
      _instance = new Singleton();
    }

    return _instance;
  }
}

Ответы [ 9 ]

24 голосов
/ 21 сентября 2010

Если два потока запускают проверку if (_instance == null) одновременно, пока не создан ни один экземпляр экземпляра, они оба попытаются вызвать new для создания экземпляра одиночного экземпляра и сохранения ссылок на них в одной переменной.

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

8 голосов
/ 21 сентября 2010

Поскольку Singleton не обеспечивает взаимное исключение свойства _instance.

Используйте блокировку для обеспечения безопасности потока:

Object thisLock = new Object();

public static Singleton Instance()
{
    lock (thisLock)
    {
        if (_instance == null)
        {
           _instance = new Singleton();
        }
    }

    return _instance;
}

Этот пример - C # - я не знаю, какой язык программирования вы используете.

http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.90).aspx

5 голосов
/ 21 сентября 2010

На основании ответа RPM1984:

Я использую следующее для объекта блокировки: object thisLock = typeof( Sinlgeton );

или просто

...
lock( typeof( Singleton ) )
{
   ...
}

за производительность подковы среди вас:

public Singleton getInstance()
{
    // the first query may save a performance-wise expensive lock - operation
    if ( null == _instance )
    {
       lock ( typeof( Singleton ) )
       {
          if ( null == _instance )
          {
             _ instance = new Singleton()
          }
       }
    }

    return _instance;
}

Кстати: это называется двойной блокировкой синглтона.

3 голосов
/ 21 сентября 2010

Поскольку возможно, что в этом случае будет создано несколько экземпляров синглтона. Предположим, что два потока вошли в метод Instance, а одноэлементный объект еще не создан (т.е. _instance равен NULL). Затем предположим, что первый поток выполняет условие if и вводит его. Но перед этим new происходит переключение контекста потока, и начинает выполняться второй поток. Он также проверяет условие if и обнаруживает, что оно равно NULL и выполняет new. Теперь начинает выполняться первый поток и создает еще один экземпляр объекта.

1 голос
/ 21 сентября 2010

Это оказалось более интересным, чем я думал изначально:

Так что лучшим решением будет

public sealed class Singleton
{
    private static readonly Singletion _instance = new Singleton();

    private Singleton()
    {
       //do your construction
    }

    public static Singleton getInstance()
    {
       return _instance;
    }
}

Из моего нынешнего понимания среда программирования (Java, .NET) не должна иметь значения для этого решения.

Есть мысли или комментарии?

Дальнейшее чтение я откопал:

Edit: Что касается Java, оно также должно работать:

Теперь, если кто-то укажет на сохраненную версию C ++, она будет завершена ... (Я слишком далеко от C ++, чтобы помнить детали ...)

0 голосов
/ 03 октября 2010

На самом деле, приведенный пример может быть безопасным для типов.Кроме того, это может быть даже достаточно хорошей идеей.

Если несколько потоков выполнят нулевую проверку до того, как первый поток записал в _instance (и, возможно, после того, как первый поток записал в _instance, но до второгоЦП потока получил новое значение, загруженное в его кэш), затем второй (и третий, и четвертый ...) поток создаст новый экземпляр и запишет это в _instance.

На языке сборки мусораэто просто означает, что на короткое время несколько потоков будут иметь собственную версию _instance, но достаточно скоро они выйдут из области видимости и получат право на сборку мусора.

Теперь это расточительно, нона самом деле это проблема или нет, зависит от того, насколько дорого стоит создание нового экземпляра и есть ли какие-либо негативные последствия наличия более одного экземпляра.Очень часто обратная сторона такого ненужного дублирования объектов экземпляра довольно незначительна, и в этом случае она может быть менее отрицательной, чем стоимость блокировки (блокировка относительно дешевая, но не бесплатная, ожидание блокировки может быть довольно дорогим, и внекоторые случаи (тупик - самый экстремальный случай), или даже CASsing.Даже если это дороже, на самом деле это может быть небезопасно.

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

Скорее всего, создание статической конструкции - это путь.

0 голосов
/ 21 сентября 2010

Если создание синглтона обходится дешево, и основное требование заключается в том, чтобы все в приложении видело одно и то же, другой подход:

  If TheSingleton Is Nothing Then
    Dim newSingleton As New Singleton
    If Interlocked.CompareExchange(TheSingleton, newSingleton, Nothing) IsNot Nothing Then
      newSingleton.Dispose  ' If applicable
    End If
  End If

Если два потока проходят тест «Если» передделает CompareExchange, будут созданы два синглета.Однако только первый синглтон, передаваемый в CompareExchange, будет использоваться для чего угодно;другой будет отброшен.

0 голосов
/ 21 сентября 2010

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

Простейшим примером является то, что поток A проверяет условное выражение, а затем уступает потоку B;Поток B проверяет то же самое условие и создает новый объект и возвращает этот новый объект;Затем поток B возвращается к потоку A, который начинает там, где остановился;Затем выполняется поток A, который создает новый объект и возвращает его.

Существуют и другие заметные проблемы:

  1. Переменная _singleton должна быть помечена как volatile, чтобы обеспечить правильную публикацию записей.
  2. Класс должен быть помечен как окончательный или запечатан, чтобы избежать проблем с подклассами.
  3. В Java вы должны переопределить метод clone (), чтобы вызвать исключение UnsupportedOperationException.
  4. по какой-то причине существует требование сделать сериализуемый класс, вам также необходимо предоставить реализацию для обработки этого сценария (в противном случае клиент может непрерывно десериализовать поток для создания нескольких экземпляров).
0 голосов
/ 21 сентября 2010

Я думаю, что в Java достаточно просто добавить синхронизированный флаг в метод getInstance.Это предотвратит попадание других потоков в метод, пока один внутри.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...