关于c#:以单例模式双重检查锁定


double check locking in singleton pattern

这可能是个基本问题

要在多线程环境中使用单实例,我们可以使用锁。请参考代码段。但是为什么我们需要单例模式中的双重检查锁定?还有,双重检查锁定意味着什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class singleton
{
    private static singleton instance = null;
    private static singleton() { }

    private static object objectlock = new object();

    public static singleton Instance
    {
        get
        {

            lock (objectlock) //single - check lock
            {
                if (instance == null)
                {
                    instance = new singleton();
                }

                return instance;
            }
        }

    }
}


乔恩·斯基特对此作了详细解释。

锁很贵。如果对象已经存在,就没有必要取出锁。因此,您可以在锁外进行第一次检查。

但是,即使在查看之前对象不存在,另一个线程也可能在if条件和lock语句之间创建了它。因此,您需要再次检查锁内。

然而,编写singleton的最佳方法是使用static构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public sealed class Singleton
{
    private Singleton()
    {
    }

    public static Singleton Instance { get { return Nested.instance; } }

    private class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}


对于.NET 4.x及更高版本,您应该尽可能地遵从lazy类,因为此模式与初始化和发布选项一起使用。(注意:如果创建不是线程安全的,但实例的发布是通过发布选项发布的,则可以使用反向函数)


多线程单线程:使用双重检查锁定的最佳方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public sealed class Singleton
{
   private static volatile Singleton _instance;
   private static readonly object InstanceLoker= new Object();

   private Singleton() {}

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

         return _instance;
      }
   }
}

我知道"最好"的方法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MySingleton {
    // object for synchronization
    private static readonly object syncRoot = new object();
    // the singleton instance
    private static MySingleton @default;

    public static MySingleton Default {
        get {
            // geting singleton instance without locking
            var result = @default;
            // if result is NOT null, no additional action is required
            if ( object.ReferenceEquals(result, null) ){
                // lock the synchronization object
                lock(syncRoot) {
                    // geting singleton instanc in lock - because
                    // the value of @default field could be changed
                    result = @default;

                    // checking for NULL
                    if ( object.ReferenceEquals(result, null) ) {
                        // if result is NULL, create new singleton instance
                        result = new MySingleton();
                        // set the default instance
                        @default = result;
                    }
                }
            }

            // return singleton instance
            return result;
        }
    }
}


当我们尝试使用并行库执行singleton类的方法时。由于在TPL中执行多线程导致概念单例模式失败,因此无法识别单例行为。为了解决这个问题,有一个锁定对象的概念,这样一次只有一个线程可以访问它。但这不是有效的方法,因为涉及锁检查会为对象创建不必要的监视。为了避免这种情况,我们使用"双重锁定检查"。

enter image description here


如果在字段初始化器中创建对象,则不需要锁:

1
2
3
4
5
6
7
8
9
10
class singleton
{
    private static singleton instance = new singleton();
    private static singleton() { }

    public static singleton Instance
    {
        get { return instance; }
    }
}

还要记住,锁只控制对象的创建,如果在多个线程中使用对象,那么对象仍然需要是线程安全的。