单件模式(Singleton Pattern)[转]

单件模式(Singleton Pattern)

——.NET设计模式系列之二

Terrylee,2005年12月07日

概述

Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点。这就提出了一个问题:如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,而不是类使用者的责任。

从另一个角度来说,Singleton模式其实也是一种职责型模式。因为我们创建了一个对象,这个对象扮演了独一无二的角色,在这个单独的对象实例中,它集中了它所属类的所有权力,同时它也肩负了行使这种权力的职责!

意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

模型图

逻辑模型图:

 

物理模型图:

 

生活中的例子

美国总统的职位是Singleton,美国宪法规定了总统的选举,任期以及继任的顺序。这样,在任何时刻只能由一个现任的总统。无论现任总统的身份为何,其头衔"美利坚合众国总统"是访问这个职位的人的一个全局的访问点。

 

五种实现

1.简单实现

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4
 5    Singleton()
 6    {
 7    }
 8
 9    public static Singleton Instance
10    {
11        get
12        {
13            if (instance==null)
14            {
15                instance = new Singleton();
16            }
17            return instance;
18        }
19    }
20}

这种方式的实现对于线程来说并不是安全的,因为在多线程的环境下有可能得到Singleton类的多个实例。如果同时有两个线程去判断(instance == null),并且得到的结果为真,这时两个线程都会创建类Singleton的实例,这样就违背了Singleton模式的原则。实际上在上述代码中,有可能在计算出表达式的值之前,对象实例已经被创建,但是内存模型并不能保证对象实例在第二个线程创建之前被发现。

该实现方式主要有两个优点:

l         由于实例是在 Instance 属性方法内部创建的,因此类可以使用附加功能(例如,对子类进行实例化),即使它可能引入不想要的依赖性。

l         直到对象要求产生一个实例才执行实例化;这种方法称为“惰性实例化”。惰性实例化避免了在应用程序启动时实例化不必要的 singleton

2.安全的线程

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4    static readonly object padlock = new object();
 5
 6    Singleton()
 7    {
 8    }
 9
10    public static Singleton Instance
11    {
12        get
13        {
14            lock (padlock)
15            {
16                if (instance==null)
17                {
18                    instance = new Singleton();
19                }
20                return instance;
21            }
22        }
23    }
24}
25
26

这种方式的实现对于线程来说是安全的。我们首先创建了一个进程辅助对象,线程在进入时先对辅助对象加锁然后再检测对象是否被创建,这样可以确保只有一个实例被创建,因为在同一个时刻加了锁的那部分程序只有一个线程可以进入。这种情况下,对象实例由最先进入的那个线程创建,后来的线程在进入时(instence == null)为假,不会再去创建对象实例了。但是这种实现方式增加了额外的开销,损失了性能。

3.双重锁定

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4    static readonly object padlock = new object();
 5
 6    Singleton()
 7    {
 8    }
 9
10    public static Singleton Instance
11    {
12        get
13        {
14            if (instance==null)
15            {
16                lock (padlock)
17                {
18                    if (instance==null)
19                    {
20                        instance = new Singleton();
21                    }
22                }
23            }
24            return instance;
25        }
26    }
27}
28

这种实现方式对多线程来说是安全的,同时线程不是每次都加锁,只有判断对象实例没有被创建时它才加锁,有了我们上面第一部分的里面的分析,我们知道,加锁后还得再进行对象是否已被创建的判断。它解决了线程并发问题,同时避免在每个 Instance 属性方法的调用中都出现独占锁定。它还允许您将实例化延迟到第一次访问对象时发生。实际上,应用程序很少需要这种类型的实现。大多数情况下我们会用静态初始化。这种方式仍然有很多缺点:无法实现延迟初始化。

4.静态初始化

 1public sealed class Singleton
 2{
 3    static readonly Singleton instance=new Singleton();
 4
 5    static Singleton()
 6    {
 7    }
 8
 9    Singleton()
10    {
11    }
12
13    public static Singleton Instance
14    {
15        get
16        {
17            return instance;
18        }
19    }
20}
21

看到上面这段富有戏剧性的代码,我们可能会产生怀疑,这还是Singleton模式吗?在此实现中,将在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化。该类标记为 sealed 以阻止发生派生,而派生可能会增加实例。此外,变量标记为 readonly,这意味着只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。

该实现与前面的示例类似,不同之处在于它依赖公共语言运行库来初始化变量。它仍然可以用来解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访问实例提供了一个全局访问点。此外,由于构造函数是私有的,因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。

由于 Singleton 实例被私有静态成员变量引用,因此在类首次被对 Instance 属性的调用所引用之前,不会发生实例化。

这种方法唯一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。

5.延迟初始化

 1public sealed class Singleton
 2{
 3    Singleton()
 4    {
 5    }
 6
 7    public static Singleton Instance
 8    {
 9        get
10        {
11            return Nested.instance;
12        }
13    }
14    
15    class Nested
16    {
17        static Nested()
18        {
19        }
20
21        internal static readonly Singleton instance = new Singleton();
22    }
23}
24

这里,初始化工作有Nested类的一个静态成员来完成,这样就实现了延迟初始化,并具有很多的优势,是值得推荐的一种实

现方式。

实现要点

l        Singleton模式是限制而不是改进类的创建。

l         Singleton类中的实例构造器可以设置为Protected以允许子类派生。

l         Singleton模式一般不要支持Icloneable接口,因为这可能导致多个对象实例,与Singleton模式的初衷违背。

l         Singleton模式一般不要支持序列化,这也有可能导致多个对象实例,这也与Singleton模式的初衷违背。

l         Singleton只考虑了对象创建的管理,没有考虑到销毁的管理,就支持垃圾回收的平台和对象的开销来讲,我们一般没必要对其销毁进行特殊的管理。

l         理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的构造器的任意调用”。

l         可以很简单的修改一个Singleton,使它有少数几个实例,这样做是允许的而且是有意义的。

优点

l         实例控制:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例

l         灵活性:因为类控制了实例化过程,所以类可以更加灵活修改实例化过程

缺点

l         开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题,上面的五种实现方式中已经说过了。

l          可能的开发混淆:使用 singleton 对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

l         对象的生存期:Singleton 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于 .NET Framework 的语言),只有 Singleton 类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除
对象实例,但这样会导致 Singleton 类中出现悬浮引用。

适用性

l         当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。

l         当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

应用场景

l         每台计算机可以有若干个打印机,但只能有一个Printer Spooler,避免两个打印作业同时输出到打印机。
(摘自吕震宇的C#设计模式(7)-Singleton Pattern

l         PC机中可能有几个串口,但只能有一个COM1口的实例。

l         系统中只能有一个窗口管理器。

l         .NET Remoting中服务器激活对象中的Sigleton对象,确保所有的客户程序的请求都只有一个实例来处理。

完整示例

这是一个简单的计数器例子,四个线程同时进行计数。

 1using System;
 2using System.Threading;
 3
 4namespace SigletonPattern.SigletonCounter
 5{
 6    /**//// <summary>
 7    /// 功能:简单计数器的单件模式
 8    /// 编写:Terrylee
 9    /// 日期:2005年12月06日
10    /// </summary>
11    public class CountSigleton
12    {
13        /**////存储唯一的实例
14        static CountSigleton uniCounter = new CountSigleton();  
15   
16        /**////存储计数值
17        private int totNum = 0;  
18   
19        private CountSigleton() 
20   
21        { 
22            /**////线程延迟2000毫秒
23            Thread.Sleep(2000);
24        } 
25   
26        static public CountSigleton Instance() 
27   
28        { 
29   
30            return uniCounter; 
31   
32        } 
33        
34        /**////计数加1
35        public void Add()
36        { 
37            totNum ++;
38        }  
39        
40        /**////获得当前计数值
41        public int GetCounter()
42        { 
43            return totNum;
44        } 
45
46    }
47}
48
 1using System;
 2using System.Threading;
 3using System.Text;
 4
 5namespace SigletonPattern.SigletonCounter
 6{
 7    /**//// <summary>
 8    /// 功能:创建一个多线程计数的类
 9    /// 编写:Terrylee
10    /// 日期:2005年12月06日
11    /// </summary>
12    public class CountMutilThread
13    {
14        public CountMutilThread()
15        {
16            
17        }
18
19        /**//// <summary>
20        /// 线程工作
21        /// </summary>
22        public static void DoSomeWork()
23        {
24            /**////构造显示字符串
25            string results = "";
26
27            /**////创建一个Sigleton实例
28            CountSigleton MyCounter = CountSigleton.Instance();
29
30            /**////循环调用四次
31            for(int i=1;i<5;i++)
32            {
33                /**////开始计数
34                MyCounter.Add();
35                
36                results +="线程";
37                results += Thread.CurrentThread.Name.ToString() + "——〉";
38                results += "当前的计数:";
39                results += MyCounter.GetCounter().ToString();
40                results += "\n";
41
42                Console.WriteLine(results);
43                
44                /**////清空显示字符串
45                results = "";
46            }
47        }
48
49        public void StartMain()
50        {
51
52            Thread thread0 = Thread.CurrentThread; 
53   
54            thread0.Name = "Thread 0"; 
55   
56            Thread thread1 =new Thread(new ThreadStart(DoSomeWork)); 
57   
58            thread1.Name = "Thread 1"; 
59   
60            Thread thread2 =new Thread(new ThreadStart(DoSomeWork)); 
61   
62            thread2.Name = "Thread 2"; 
63   
64            Thread thread3 =new Thread(new ThreadStart(DoSomeWork)); 
65   
66            thread3.Name = "Thread 3"; 
67   
68            thread1.Start(); 
69   
70            thread2.Start(); 
71   
72            thread3.Start(); 
73            
74            /**////线程0也只执行和其他线程相同的工作
75            DoSomeWork(); 
76        }
77    }
78}
79
 1using System;
 2using System.Text;
 3using System.Threading;
 4
 5namespace SigletonPattern.SigletonCounter
 6{
 7    /**//// <summary>
 8    /// 功能:实现多线程计数器的客户端
 9    /// 编写:Terrylee
10    /// 日期:2005年12月06日
11    /// </summary>
12    public class CountClient
13    {
14        public static void Main(string[] args)
15        {
16       CountMutilThread cmt = new CountMutilThread();
17
18            cmt.StartMain();
19
20            Console.ReadLine();
21        }
22    }
23}
24

总结

Singleton设计模式是一个非常有用的机制,可用于在面向对象的应用程序中提供单个访问点。文中通过五种实现方式的比较和一个完整的示例,完成了对Singleton模式的一个总结和探索。用一句广告词来概括Singleton模式就是“简约而不简单”。

_________________________________________________________________________________________________
源码下载:/Files/Terrylee/SigletonPattern.rar

参考文献:

《C#计模式》,中国电力出版社

使用 Microsoft .NET 的企业解决方案模式

《Implementing the Singleton Pattern in C#》

MSDN《Exploring the Singleton Design Pattern》

吕震宇C#设计模式(7)-Singleton Pattern

C#的Singleton设计模式

引用1-------------------------------------------------------------------------------------------

多线程编程中的锁定(lock,Monitor)

多线程是一个非常好的技术,当然前提是我们正确地使用它。

在多线程编程中最难控制的就是对于同一个对象的并发访问(读写),如果不加以注意,那么就很有可能发生一些意料不到的情况。

为了防止或者说尽量减少并发问题,我们使用类似于数据库给数据加锁的机制来实现。

我们下面用例子来说明这个问题. 首先我们看一下标准的写法。我们用五个线程去做循环,他们都只做一件事情,修改一个公用变量(count)的值。

using System;
using System.Threading;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
private static  int count = 0;
private static object syncroot = new object();
static void Main(string[] args)
{
StreamWriter sw = new
StreamWriter(DateTime.Now.ToLongTimeString().Replace(':','-') + ".log", true);
Console.SetOut(sw);
Thread thread1 = new Thread(new ThreadStart(SomeMethod));
Thread thread2 = new Thread(new ThreadStart(SomeMethod));
Thread thread3 = new Thread(new ThreadStart(SomeMethod));
Thread thread4 = new Thread(new ThreadStart(SomeMethod));
Thread thread5 = new Thread(new ThreadStart(SomeMethod));
thread1.Name = "Thread 1";
thread2.Name = "Thread 2";
thread3.Name = "Thread 3";
thread4.Name = "Thread 4";
thread5.Name = "Thread 5";
thread1.Start();
while (!thread1.IsAlive)
Thread.Sleep(10);
thread2.Start();
while (!thread2.IsAlive)
Thread.Sleep(100);
thread3.Start();
while (!thread3.IsAlive)
Thread.Sleep(100);
thread4.Start();
while (!thread4.IsAlive)
Thread.Sleep(100);
thread5.Start();
while (!thread5.IsAlive)
Thread.Sleep(100);
//分别启动了五个线程,做同样的事情
thread1.Join();
thread2.Join();
thread3.Join();
thread4.Join();
thread5.Join();
//让主线程等待这五个线程全部结束
sw.Close();
}
static void SomeMethod() {
Random rnd = new Random();
for (int i = 0; i < 10; i++)
{
int currentvalue = ++count;//给公用变量count递增1,并且将递增之后的结果赋给另外一个变量
Thread.Sleep(rnd.Next(100));//随机休眠0.1秒以内的时间
string output =
string.Format("当前线程:{0},当前值:{1},是否被别的线程篡改:{2}",
Thread.CurrentThread.Name, count, currentvalue != count);
Console.WriteLine(output);
}
}
}
}
我们来看最后的输出结果

当前线程:Thread 1,当前值:5,是否被别的线程篡改:True
当前线程:Thread 3,当前值:6,是否被别的线程篡改:True
当前线程:Thread 5,当前值:7,是否被别的线程篡改:True
当前线程:Thread 2,当前值:8,是否被别的线程篡改:True
当前线程:Thread 4,当前值:9,是否被别的线程篡改:True
当前线程:Thread 2,当前值:10,是否被别的线程篡改:True
当前线程:Thread 4,当前值:11,是否被别的线程篡改:True
当前线程:Thread 1,当前值:11,是否被别的线程篡改:True
当前线程:Thread 3,当前值:13,是否被别的线程篡改:True
当前线程:Thread 5,当前值:14,是否被别的线程篡改:True
当前线程:Thread 1,当前值:15,是否被别的线程篡改:True
当前线程:Thread 3,当前值:16,是否被别的线程篡改:True
当前线程:Thread 5,当前值:17,是否被别的线程篡改:True
当前线程:Thread 2,当前值:18,是否被别的线程篡改:True
当前线程:Thread 4,当前值:19,是否被别的线程篡改:True
当前线程:Thread 2,当前值:20,是否被别的线程篡改:True
当前线程:Thread 4,当前值:21,是否被别的线程篡改:True
当前线程:Thread 1,当前值:22,是否被别的线程篡改:True
当前线程:Thread 3,当前值:22,是否被别的线程篡改:True
当前线程:Thread 5,当前值:24,是否被别的线程篡改:True
当前线程:Thread 1,当前值:25,是否被别的线程篡改:True
当前线程:Thread 3,当前值:26,是否被别的线程篡改:True
当前线程:Thread 5,当前值:27,是否被别的线程篡改:True
当前线程:Thread 2,当前值:28,是否被别的线程篡改:True
当前线程:Thread 4,当前值:29,是否被别的线程篡改:True
当前线程:Thread 2,当前值:30,是否被别的线程篡改:True
当前线程:Thread 4,当前值:31,是否被别的线程篡改:True
当前线程:Thread 1,当前值:32,是否被别的线程篡改:True
当前线程:Thread 3,当前值:33,是否被别的线程篡改:True
当前线程:Thread 5,当前值:34,是否被别的线程篡改:True
当前线程:Thread 1,当前值:35,是否被别的线程篡改:True
当前线程:Thread 3,当前值:36,是否被别的线程篡改:True
当前线程:Thread 5,当前值:37,是否被别的线程篡改:True
当前线程:Thread 2,当前值:38,是否被别的线程篡改:True
当前线程:Thread 4,当前值:39,是否被别的线程篡改:True
当前线程:Thread 2,当前值:40,是否被别的线程篡改:True
当前线程:Thread 4,当前值:41,是否被别的线程篡改:True
当前线程:Thread 1,当前值:42,是否被别的线程篡改:True
当前线程:Thread 3,当前值:43,是否被别的线程篡改:True
当前线程:Thread 5,当前值:44,是否被别的线程篡改:True
当前线程:Thread 2,当前值:45,是否被别的线程篡改:True
当前线程:Thread 4,当前值:46,是否被别的线程篡改:True
当前线程:Thread 1,当前值:46,是否被别的线程篡改:True
当前线程:Thread 3,当前值:47,是否被别的线程篡改:True
当前线程:Thread 5,当前值:49,是否被别的线程篡改:True
当前线程:Thread 4,当前值:50,是否被别的线程篡改:True
当前线程:Thread 1,当前值:50,是否被别的线程篡改:True
当前线程:Thread 2,当前值:50,是否被别的线程篡改:True
当前线程:Thread 3,当前值:50,是否被别的线程篡改:True
当前线程:Thread 5,当前值:50,是否被别的线程篡改:False

让人震惊的是,只有最后一个输出的时候,才没有被篡改。就是说50次操作里面,只有一次也就是最后一次是保证了业务的要求的。而且你注意看每次书出来的当前值,很多是重复的。这是怎么回事呢?

其他的操作为什么会出现被别的线程篡改的情况呢?很简单,我们让它休眠了一会儿,在它休眠的时候,别的线程获得了访问权限,并进行了修改)。

注意,我们这里用休眠只是为了演示目的,现实生活中的例子就好比说,你先对一个变量进行了操作,然后又要进行其他的操作(这肯定也需要时间),然后最后还需要对之前那个变量进行读取。

那么,有没有什么方法可以防止这种问题出现吗?答案是肯定的,.NET内置对此进行支持。我们主要介绍两个方法

1.使用lock语句(该语法是C#专有的)

using System;
using System.Threading;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
private static  int count = 0;
private static object syncroot = new object();
static void Main(string[] args)
{
StreamWriter sw = new
StreamWriter(DateTime.Now.ToLongTimeString().Replace(':','-') + ".log", true);
Console.SetOut(sw);
Thread thread1 = new Thread(new ThreadStart(SomeMethod));
Thread thread2 = new Thread(new ThreadStart(SomeMethod));
Thread thread3 = new Thread(new ThreadStart(SomeMethod));
Thread thread4 = new Thread(new ThreadStart(SomeMethod));
Thread thread5 = new Thread(new ThreadStart(SomeMethod));
thread1.Name = "Thread 1";
thread2.Name = "Thread 2";
thread3.Name = "Thread 3";
thread4.Name = "Thread 4";
thread5.Name = "Thread 5";
thread1.Start();
while (!thread1.IsAlive)
Thread.Sleep(10);
thread2.Start();
while (!thread2.IsAlive)
Thread.Sleep(100);
thread3.Start();
while (!thread3.IsAlive)
Thread.Sleep(100);
thread4.Start();
while (!thread4.IsAlive)
Thread.Sleep(100);
thread5.Start();
while (!thread5.IsAlive)
Thread.Sleep(100);
//分别启动了五个线程,做同样的事情
thread1.Join();
thread2.Join();
thread3.Join();
thread4.Join();
thread5.Join();
//让主线程等待这五个线程全部结束
sw.Close();
}
static void SomeMethod() {
Random rnd = new Random();
for (int i = 0; i < 10; i++)
{
lock (syncroot)
{
int currentvalue = ++count;
//给公用变量count递增1,并且将递增之后的结果赋给另外一个变量
Thread.Sleep(rnd.Next(100));
//随机休眠0.1秒以内的时间
string output =
string.Format("当前线程:{0},当前值:{1},是否被别的线程篡改:{2}",
Thread.CurrentThread.Name, count, currentvalue != count);
Console.WriteLine(output);
}
}
}
}
}

值得注意的是,lock语句里面的变量必须是引用类型,而不能是值类型。所以这里,我们不能lock(count),而可以随便给一个object变量来进行lock,那么lock到底什么意思呢?就是在lock语句块里面的所有代码,所涉及到的变量都将被加锁,直到该语句块退出(就是说执行到lock语句块的那个"}" 后面的时候)

我们来看看输出结果

当前线程:Thread 1,当前值:1,是否被别的线程篡改:False
当前线程:Thread 2,当前值:2,是否被别的线程篡改:False
当前线程:Thread 3,当前值:3,是否被别的线程篡改:False
当前线程:Thread 4,当前值:4,是否被别的线程篡改:False
当前线程:Thread 5,当前值:5,是否被别的线程篡改:False
当前线程:Thread 1,当前值:6,是否被别的线程篡改:False
当前线程:Thread 2,当前值:7,是否被别的线程篡改:False
当前线程:Thread 3,当前值:8,是否被别的线程篡改:False
当前线程:Thread 4,当前值:9,是否被别的线程篡改:False
当前线程:Thread 5,当前值:10,是否被别的线程篡改:False
当前线程:Thread 1,当前值:11,是否被别的线程篡改:False
当前线程:Thread 2,当前值:12,是否被别的线程篡改:False
当前线程:Thread 3,当前值:13,是否被别的线程篡改:False
当前线程:Thread 4,当前值:14,是否被别的线程篡改:False
当前线程:Thread 5,当前值:15,是否被别的线程篡改:False
当前线程:Thread 1,当前值:16,是否被别的线程篡改:False
当前线程:Thread 2,当前值:17,是否被别的线程篡改:False
当前线程:Thread 3,当前值:18,是否被别的线程篡改:False
当前线程:Thread 4,当前值:19,是否被别的线程篡改:False
当前线程:Thread 5,当前值:20,是否被别的线程篡改:False
当前线程:Thread 1,当前值:21,是否被别的线程篡改:False
当前线程:Thread 2,当前值:22,是否被别的线程篡改:False
当前线程:Thread 3,当前值:23,是否被别的线程篡改:False
当前线程:Thread 4,当前值:24,是否被别的线程篡改:False
当前线程:Thread 5,当前值:25,是否被别的线程篡改:False
当前线程:Thread 1,当前值:26,是否被别的线程篡改:False
当前线程:Thread 2,当前值:27,是否被别的线程篡改:False
当前线程:Thread 3,当前值:28,是否被别的线程篡改:False
当前线程:Thread 4,当前值:29,是否被别的线程篡改:False
当前线程:Thread 5,当前值:30,是否被别的线程篡改:False
当前线程:Thread 1,当前值:31,是否被别的线程篡改:False
当前线程:Thread 2,当前值:32,是否被别的线程篡改:False
当前线程:Thread 3,当前值:33,是否被别的线程篡改:False
当前线程:Thread 4,当前值:34,是否被别的线程篡改:False
当前线程:Thread 5,当前值:35,是否被别的线程篡改:False
当前线程:Thread 1,当前值:36,是否被别的线程篡改:False
当前线程:Thread 2,当前值:37,是否被别的线程篡改:False
当前线程:Thread 3,当前值:38,是否被别的线程篡改:False
当前线程:Thread 4,当前值:39,是否被别的线程篡改:False
当前线程:Thread 5,当前值:40,是否被别的线程篡改:False
当前线程:Thread 1,当前值:41,是否被别的线程篡改:False
当前线程:Thread 2,当前值:42,是否被别的线程篡改:False
当前线程:Thread 3,当前值:43,是否被别的线程篡改:False
当前线程:Thread 4,当前值:44,是否被别的线程篡改:False
当前线程:Thread 5,当前值:45,是否被别的线程篡改:False
当前线程:Thread 1,当前值:46,是否被别的线程篡改:False
当前线程:Thread 2,当前值:47,是否被别的线程篡改:False
当前线程:Thread 3,当前值:48,是否被别的线程篡改:False
当前线程:Thread 4,当前值:49,是否被别的线程篡改:False
当前线程:Thread 5,当前值:50,是否被别的线程篡改:False

一点都不奇怪,现在输出的值是连续的,从1到50,而且没有任何一个值被篡改过了。为什么呢?它没有机会被篡改,因为在某个线程使用它的时候,它就被锁定了,别的线程必须等待这个锁被释放才可以继续进行更改。

所以,基于这样的原因,使用了lock之后,效率会稍微低一点。这是肯定的。

2.使用Monitor对象。刚才说到了lock语句是c#专用的,vb.net并没有该语法(vb.net确实也有一个Lock方法,但那个Lock是对文件进行锁定的),为了保证通用性,除了使用lock语句,还可以使用Moniter对象

using System;
using System.Threading;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
private static  int count = 0;
private static object syncroot = new object();
static void Main(string[] args)
{
StreamWriter sw = new
StreamWriter(DateTime.Now.ToLongTimeString().Replace(':','-') + ".log", true);
Console.SetOut(sw);
Console.WriteLine("开始时间:" + DateTime.Now.ToLongTimeString());
Thread thread1 = new Thread(new ThreadStart(SomeMethod));
Thread thread2 = new Thread(new ThreadStart(SomeMethod));
Thread thread3 = new Thread(new ThreadStart(SomeMethod));
Thread thread4 = new Thread(new ThreadStart(SomeMethod));
Thread thread5 = new Thread(new ThreadStart(SomeMethod));
thread1.Name = "Thread 1";
thread2.Name = "Thread 2";
thread3.Name = "Thread 3";
thread4.Name = "Thread 4";
thread5.Name = "Thread 5";
thread1.Start();
while (!thread1.IsAlive)
Thread.Sleep(10);
thread2.Start();
while (!thread2.IsAlive)
Thread.Sleep(100);
thread3.Start();
while (!thread3.IsAlive)
Thread.Sleep(100);
thread4.Start();
while (!thread4.IsAlive)
Thread.Sleep(100);
thread5.Start();
while (!thread5.IsAlive)
Thread.Sleep(100);
//分别启动了五个线程,做同样的事情
thread1.Join();
thread2.Join();
thread3.Join();
thread4.Join();
thread5.Join();
//让主线程等待这五个线程全部结束
Console.WriteLine("结束时间:" + DateTime.Now.ToLongTimeString());
sw.Close();
}
static void SomeMethod() {
Random rnd = new Random();
for (int i = 0; i < 10; i++)
{
try
{
Monitor.Enter(syncroot);
int currentvalue = ++count;
//给公用变量count递增1,并且将递增之后的结果赋给另外一个变量
Thread.Sleep(rnd.Next(200));
//随机休眠0.2秒以内的时间
string output =
string.Format("当前线程:{0},当前值:{1},是否被别的线程篡改:{2}",
Thread.CurrentThread.Name, count, currentvalue != count);
Console.WriteLine(output);
}
catch { }
finally
{
Monitor.Exit(syncroot);
}
}
}
}
}

输出结果如下

当前线程:Thread 1,当前值:1,是否被别的线程篡改:False
当前线程:Thread 3,当前值:2,是否被别的线程篡改:False
当前线程:Thread 1,当前值:3,是否被别的线程篡改:False
当前线程:Thread 5,当前值:4,是否被别的线程篡改:False
当前线程:Thread 2,当前值:5,是否被别的线程篡改:False
当前线程:Thread 4,当前值:6,是否被别的线程篡改:False
当前线程:Thread 3,当前值:7,是否被别的线程篡改:False
当前线程:Thread 1,当前值:8,是否被别的线程篡改:False
当前线程:Thread 5,当前值:9,是否被别的线程篡改:False
当前线程:Thread 2,当前值:10,是否被别的线程篡改:False
当前线程:Thread 4,当前值:11,是否被别的线程篡改:False
当前线程:Thread 3,当前值:12,是否被别的线程篡改:False
当前线程:Thread 1,当前值:13,是否被别的线程篡改:False
当前线程:Thread 5,当前值:14,是否被别的线程篡改:False
当前线程:Thread 2,当前值:15,是否被别的线程篡改:False
当前线程:Thread 4,当前值:16,是否被别的线程篡改:False
当前线程:Thread 3,当前值:17,是否被别的线程篡改:False
当前线程:Thread 1,当前值:18,是否被别的线程篡改:False
当前线程:Thread 5,当前值:19,是否被别的线程篡改:False
当前线程:Thread 2,当前值:20,是否被别的线程篡改:False
当前线程:Thread 4,当前值:21,是否被别的线程篡改:False
当前线程:Thread 3,当前值:22,是否被别的线程篡改:False
当前线程:Thread 1,当前值:23,是否被别的线程篡改:False
当前线程:Thread 5,当前值:24,是否被别的线程篡改:False
当前线程:Thread 2,当前值:25,是否被别的线程篡改:False
当前线程:Thread 4,当前值:26,是否被别的线程篡改:False
当前线程:Thread 3,当前值:27,是否被别的线程篡改:False
当前线程:Thread 1,当前值:28,是否被别的线程篡改:False
当前线程:Thread 5,当前值:29,是否被别的线程篡改:False
当前线程:Thread 2,当前值:30,是否被别的线程篡改:False
当前线程:Thread 4,当前值:31,是否被别的线程篡改:False
当前线程:Thread 3,当前值:32,是否被别的线程篡改:False
当前线程:Thread 1,当前值:33,是否被别的线程篡改:False
当前线程:Thread 5,当前值:34,是否被别的线程篡改:False
当前线程:Thread 2,当前值:35,是否被别的线程篡改:False
当前线程:Thread 4,当前值:36,是否被别的线程篡改:False
当前线程:Thread 3,当前值:37,是否被别的线程篡改:False
当前线程:Thread 1,当前值:38,是否被别的线程篡改:False
当前线程:Thread 5,当前值:39,是否被别的线程篡改:False
当前线程:Thread 2,当前值:40,是否被别的线程篡改:False
当前线程:Thread 4,当前值:41,是否被别的线程篡改:False
当前线程:Thread 3,当前值:42,是否被别的线程篡改:False
当前线程:Thread 1,当前值:43,是否被别的线程篡改:False
当前线程:Thread 5,当前值:44,是否被别的线程篡改:False
当前线程:Thread 2,当前值:45,是否被别的线程篡改:False
当前线程:Thread 4,当前值:46,是否被别的线程篡改:False
当前线程:Thread 3,当前值:47,是否被别的线程篡改:False
当前线程:Thread 5,当前值:48,是否被别的线程篡改:False
当前线程:Thread 2,当前值:49,是否被别的线程篡改:False
当前线程:Thread 4,当前值:50,是否被别的线程篡改:False

一点都不意外,它的结论是和lock一样的。

那么,话说回来,lock与monitor到底有什么区别吗?答案是:没有啥区别。lock只是为了方便c#开发人员的一个快捷语法,实际上它在编译的时候会自动转换为monitor代码。所以,我们就经常强调,不同语言它们也许会有一些特殊的语法,但他们最终大多都是使用通用的.net framework 的功能来做某件事情。lock属于语言级别的概念,而monitor是属于.net framework 标准类库对象。

我们可以用ILDasm这个工具,来查看一下lock语句最后是怎么编译的

image

.method private hidebysig static void  SomeMethod() cil managed
{
// 代码大小       145 (0x91)
.maxstack  5
.locals init ([0] class [mscorlib]System.Random rnd,
[1] int32 i,
[2] int32 currentvalue,
[3] string output,
[4] object CS$2$0000,
[5] bool CS$4$0001)
IL_0000:  nop
IL_0001:  newobj     instance void [mscorlib]System.Random::.ctor()
IL_0006:  stloc.0
IL_0007:  ldc.i4.0
IL_0008:  stloc.1
IL_0009:  br.s       IL_0082
IL_000b:  nop
IL_000c:  ldsfld     object ConsoleApplication1.Program::syncroot
IL_0011:  dup
IL_0012:  stloc.s    CS$2$0000
IL_0014:  call       void [mscorlib]System.Threading.Monitor::Enter(object)
IL_0019:  nop
.try
{
IL_001a:  nop
IL_001b:  ldsfld     int32 ConsoleApplication1.Program::count
IL_0020:  ldc.i4.1
IL_0021:  add
IL_0022:  dup
IL_0023:  stsfld     int32 ConsoleApplication1.Program::count
IL_0028:  stloc.2
IL_0029:  ldloc.0
IL_002a:  ldc.i4     0xc8
IL_002f:  callvirt   instance int32 [mscorlib]System.Random::Next(int32)
IL_0034:  call       void [mscorlib]System.Threading.Thread::Sleep(int32)
IL_0039:  nop
IL_003a:  ldstr      bytearray (53 5F 4D 52 BF 7E 0B 7A 3A 00 7B 00 30 00 7D 00   // S_MR.~.z:.{.0.}.
2C 00 53 5F 4D 52 3C 50 3A 00 7B 00 31 00 7D 00   // ,.S_MR<P:.{.1.}.
2C 00 2F 66 26 54 AB 88 2B 52 84 76 BF 7E 0B 7A   // ,./f&T..+R.v.~.z
E1 7B 39 65 3A 00 7B 00 32 00 7D 00 )             // .{9e:.{.2.}.
IL_003f:  call       class [mscorlib]System.Threading.Thread 
[mscorlib]System.Threading.Thread::get_CurrentThread()
IL_0044:  callvirt   instance string [mscorlib]System.Threading.Thread::get_Name()
IL_0049:  ldsfld     int32 ConsoleApplication1.Program::count
IL_004e:  box        [mscorlib]System.Int32
IL_0053:  ldloc.2
IL_0054:  ldsfld     int32 ConsoleApplication1.Program::count
IL_0059:  ceq
IL_005b:  ldc.i4.0
IL_005c:  ceq
IL_005e:  box        [mscorlib]System.Boolean
IL_0063:  call       string [mscorlib]System.String::Format(string,
object,
object,
object)
IL_0068:  stloc.3
IL_0069:  ldloc.3
IL_006a:  call       void [mscorlib]System.Console::WriteLine(string)
IL_006f:  nop
IL_0070:  nop
IL_0071:  leave.s    IL_007c
}  // end .try
finally
{
IL_0073:  ldloc.s    CS$2$0000
IL_0075:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
IL_007a:  nop
IL_007b:  endfinally
}  // end handler
IL_007c:  nop
IL_007d:  nop
IL_007e:  ldloc.1
IL_007f:  ldc.i4.1
IL_0080:  add
IL_0081:  stloc.1
IL_0082:  ldloc.1
IL_0083:  ldc.i4.s   10
IL_0085:  clt
IL_0087:  stloc.s    CS$4$0001
IL_0089:  ldloc.s    CS$4$0001
IL_008b:  brtrue     IL_000b
IL_0090:  ret
} // end of method Program::SomeMethod

太有意思了,它就是使用了try...catch...finally的方法,使用了monitor的Enter和Exit方法

那么,难道真的一点区别都没有吗?也不是啦,lock是一个最简单的monitor的实现,实际上monitor还有其他一些用法

例如:

Monitor.TryEnter ,这个方法可以指定等待的超时时间,如果指定的超时时间已经到了,仍然得不到锁,就返回false。否则,返回true. 如果把超时时间设置为0,则表示无限期等待(相当于Enter方法)

Monitor.Pulse 方法,通知等待队列中的线程锁定对象状态的更改。

Monitor.PulseAll 方法,通知所有的等待线程对象状态的更改。

注意,Pulse并不释放锁,只是给出一个信号灯,表示即将释放锁。其他等待的线程可以进行所谓的"就绪队列"。

Monitor.Wait 方法,释放对象上的锁并阻止当前线程,直到它重新获取该锁。

-------------------------------------------------------------------------------------------------------

单件模式

单件模式是一种用于确保整个应用程序中只有一个类实例且这个实例所占资源在整个应用程序中是共享时的程序设计方法(根据实际情况,可能需要几个类实例)。在某些情况下,这种程序设计方法是很有用处的。
 

目录

设计模式-单件模式(singleton)
  1. 单件模式应该使用在什么场合
  2. 单件模式类的创建
Singleton模式的实现
展开
 

设计模式-单件模式(singleton)

单件模式应该使用在什么场合

  当需要控制一个类的实例数量且调用者可以从一个公共的众所周知的访问点访问时。

单件模式类的创建

  我们分两种方式来讨论一个单件类的创建,一是将一个类的公共构造函数改为私有,另一种方式是保留类的公共构造函数,通过一个静态成员来决定是否要返回一个类实例。
 
  Singleton模式
 
  Singleton可以说是《Design Pattern》中最简单也最实用的一个设计模式。那么,什么是Singleton?
 
  顾名思义,Singleton就是确保一个类只有唯一的一个实例。Singleton主要用于对象的创建,这意味着,如果某个类采用了Singleton模式,则在这个类被创建后,它将有且仅有一个实例可供访问。很多时候我们都会需要Singleton模式,最常见的比如我们希望整个应用程序中只有一个连接数据库的Connection实例;又比如要求一个应用程序中只存在某个用户数据结构的唯一实例。我们都可以通过应用Singleton模式达到目的。
 
  一眼看去,Singleton似乎有些像全局对象。但是实际上,并不能用全局对象代替Singleton模式,这是因为:其一,大量使用全局对象会使得程序质量降低,而且有些编程语言例如C#,根本就不支持全局变量。其二,全局对象的方法并不能阻止人们将一个类实例化多次:除了类的全局实例外,开发人员仍然可以通过类的构造函数创建类的多个局部实例。而Singleton模式则通过从根本上控制类的创建,将"保证只有一个实例"这个任务交给了类本身,开发人员不可能再有其它途径得到类的多个实例。这一点是全局对象方法与Singleton模式的根本区别。
 

Singleton模式的实现

  Singleton模式的实现基于两个要点:
 
  1)不直接用类的构造函数,而另外提供一个Public的静态方法来构造类的实例。通常这个方法取名为Instance。Public保证了它的全局可见性,静态方法保证了不会创建出多余的实例。
 
  2)将类的构造函数设为Private,即将构造函数"隐藏"起来,任何企图使用构造函数创建实例的方法都将报错。这样就阻止了开发人员绕过上面的Instance方法直接创建类的实例。
 
  通过以上两点就可以完全控制类的创建:无论有多少地方需要用到这个类,它们访问的都是类的唯一生成的那个实例。以下C#代码展现了两种实现Singleton模式的方式,开发人员可以根据喜好任选其一。
 
  实现方式一:Singleton.cs
 
  using System;
 
  class SingletonDemo
 
  { private static SingletonDemo theSingleton = null;
 
  private SingletonDemo() {}
 
  public static SingletonDemo Instance()
 
  { if (null == theSingleton)
 
  {
 
  theSingleton = new SingletonDemo();
 
  }
 
  return theSingleton;
 
  }
 
  static void Main(string[] args)
 
  { SingletonDemo s1 = SingletonDemo.Instance();
 
  SingletonDemo s2 = SingletonDemo.Instance();
 
  if (s1.Equals(s2))
 
  { Console.WriteLine("see, only one instance!");
 
  }
 
  }
 
  }
 
  与之等价的另外一种实现方式是:Singleton.cs:
 
  using System;
 
  class SingletonDemo
 
  { private static SingletonDemo theSingleton = new SingletonDemo();
 
  private SingletonDemo() {}
 
  public static SingletonDemo Instance()
 
  { return theSingleton;
 
  }
 
  static void Main(string[] args)
 
  { SingletonDemo s1 = SingletonDemo.Instance();
 
  SingletonDemo s2 = SingletonDemo.Instance();
 
  if (s1.Equals(s2))
 
  { Console.WriteLine("see, only one instance!");
 
  }
 
  }
 
  }
 
  编译执行:
 
  Csc Singleton.cs
 
  得到运行结果:
 
  see, only one instance!
 
  .NET中的Singleton
 
  因为Singleton模式具有这样实用的价值,开发人员除了可以在程序代码中直接使用Singleton模式外,在许多大型系统的实现上也都处处可见它的影子。在微软隆重推出的.NET框架中,同样也可以发现Singleton思想闪烁的光芒。
 
  举例来说,在.NET框架的重要组成部分Remoting中,远程对象(Remote Object)有两种激活方式:服务器端激活方式和客户端激活方式。采用服务器端激活方式的对象又分为两种类型:Singleton对象和SingleCall对象。Singleton 对象是这样的对象:无论该对象有多少个客户端调用,它总是只有一个实例,由这个实例来处理所有的客户端请求。相反地,若将远程对象声明为 SingleCall,则系统会为每次客户端方法的调用创建一个新对象,即使这些方法调用来自同一个客户端,也即,对象只在方法调用持续期间存在,一旦方法调用结束,该对象就会被销毁。显而易见,这里的Singleton对象就是设计模式Singleton思想在.NET中的应用。
 
  那么,如何在.NET的Remoting中利用Singleton?.NET提供了两种方式将一个远程对象注册为Singleton:直接调用RegisterWellKnownServiceType方法,在参数中指定对象类型为Singleton;或在配置文件web.config中设定远程对象的类型为Singleton。这两种方法的效果相同,所不同的是后一种方法显得更加方便,因为改变配置文件的内容后,不必重新编译应用程序。下列代码显示了如何使用RegisterWellKnownServiceType方法注册远程对象类型:
 
  RemotingConfiguration.RegisterWellKnownServiceType( Type.GetType("RemotingSamples.HelloServer,object"), "SayHello", WellKnownObjectMode.Singleton);
 
  参数"SayHello"是客户端访问远程对象(这里是HelloServer)时用来代表远程对象的URI,例如tcp://localhost:8085/SayHello(假设使用的是TCP通道)。
 
  最后一个参数就指明了这个远程对象是Singleton类型。一旦将远程对象注册为Singleton,则在第一次客户端调用HelloServer的方法时创建这个远程对象,然后保持它直到客户端中断连接或对象超时被销毁为止。在此期间,无论有多少个客户端调用这个远处对象,所有的客户请求都将由那个已经存在的唯一实例接受处理。
 
  这就是Singleton在.NET中的应用。
 
  从Singleton模式的实现和应用中也可以看出,优秀的设计模式往往都具有"简约之美"。它们采用一种"优雅"的方式,将那些成功的设计方法和体系结构能够得以被简单、方便地复用。这也是为什么现在的软件开发日益强调"设计模式"的原因之所在。如果想进一步了解更多的设计模式,还是推荐各位阅读Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides的经典之作《Design Pattern》
原文地址:https://www.cnblogs.com/kingangWang/p/2276804.html