LoveCherry

技术无极限

博客园 首页 新随笔 联系 订阅 管理
  192 Posts :: 0 Stories :: 3239 Comments :: 656 Trackbacks

 

无废话C#设计模式之二:Singleton

 

意图

 

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

 

场景

 

       我们现在要做一个网络游戏的服务端程序,需要考虑怎么样才能承载大量的用户。在做WEB程序的时候有各种负载均衡的方案,不管是通过硬件实现还是软件实现,基本的思想就是有一个统一的入口,然后由它来分配用户到各个服务器上去。

       需要考虑的问题是,即使在多线程的并发状态下,用户只能通过一个唯一的入口来分配,由此引入了Singleton模式来实现这个唯一的入口。

 

示例代码

  

using System;

using System.Collections.Generic;

using System.Threading;

 

namespace SingletonExample

{

    class Program

    {

        static void Main(string[] args)

        {

            ParameterizedThreadStart ts = new ParameterizedThreadStart(EnterPlayer);

            for (int i = 0; i < 20; i++)

            {               

                Thread t = new Thread(ts);

                t.Start("player" + i);

            }

 

            LoadBalanceServer.GetLoadBalanceServer().ShowServerInfo();

     

        }

 

        static void EnterPlayer(object playerName)

        {

            LoadBalanceServer lbs = LoadBalanceServer.GetLoadBalanceServer();

            lbs.GetLobbyServer().EnterPlayer(playerName.ToString());

        }

    }

 

    class LoadBalanceServer

    {

        private const int SERVER_COUNT = 3;

        private List<LobbyServer> serverList = new List<LobbyServer>();

 

        private static volatile LoadBalanceServer lbs;

        private static object syncLock = new object();

 

        private LoadBalanceServer()

        {

            for (int i = 0; i < SERVER_COUNT; i++)

            {

                serverList.Add(new LobbyServer("LobbyServer" + i));

            }

        }

 

        public static LoadBalanceServer GetLoadBalanceServer()

        {

            if (lbs == null)

            {

                lock (syncLock)

                {

                    if (lbs == null)

                    {

                        Thread.Sleep(100);

                        lbs = new LoadBalanceServer();

                    }

                }

            }

            return lbs;

        }

 

        public LobbyServer GetLobbyServer()

        {

            LobbyServer ls = serverList[0];

            for (int i = 1; i < SERVER_COUNT; i++)

            {

                if (serverList[i].PlayerList.Count < ls.PlayerList.Count)

                    ls = serverList[i];

            }

            return ls;

        }

 

        public void ShowServerInfo()

        {

            foreach (LobbyServer ls in serverList)

            {

                Console.WriteLine("=================" + ls.ServerName + "=================");

                foreach (string player in ls.PlayerList)

                {

                    Console.WriteLine(player);

                }

            }

        }

    }

 

    class LobbyServer

    {

        private List<string> playerList = new List<string>();

 

        public List<string> PlayerList

        {

            get { return playerList; }

        }

 

        private string serverName;

 

        public string ServerName

        {

            get { return serverName; }

        }

 

        public LobbyServer(string serverName)

        {

            this.serverName = serverName;

        }

 

        public void EnterPlayer(string playerName)

        {

            playerList.Add(playerName);

        }

    }

}

 

 

代码执行结果如下图:

 

 

代码说明

 

l         LoadBalanceServer类实现了Singleton模式,也就是说无论在什么情况下,只会有一个LoadBalanceServer类的实例出现。

l         LobbyServer类表示大厅服务,用户进入大厅后和大厅服务进行服务,在这里我们仅仅在大厅服务里面保存了用户列表。

l         Singleton模式有很多实现方式,在这里使用的是双重锁定方式。对于C#来说,可能使用静态初始化方式是最简洁的,这里就不演示了。

l         LoadBalanceServer类的GetLobbyServer()方法负责返回一个压力最小的LobbyServer对象。

l         实例化LoadBalanceServer的时候Sleep了线程,目的是模拟高并发的情况,在正式代码中没有必要这样做。

 

何时采用

 

l         从代码角度来说,当你希望类只有一个实例的时候。

l         从应用角度来说,你希望有一个总管来负责某一件事情。并且这件事情的分配只能有一个人进行,如果有多个人进行肯定会弄乱。比如创建处理流水号如果有两个地方在创建的话是不是就会重复了呢?

 

实现要点

 

l         一个Singleton类,它能确保自身的实例是唯一的。

 

注意事项

 

l         不要滥用Singleton模式,只有非一个实例不可的情况下才考虑引入Singleton。否则,程序的可扩展性可能会受到限制。

posted on 2007-10-05 13:36 lovecherry 阅读(12252) 评论(34) 编辑 收藏

Feedback

写得不错!只是LobbyServer有点瑕疵

public List<string> PlayerList
{
get { return playerList; }
}

private string serverName;
public string ServerName
{
get { return serverName; }
}

这两个属性只需只读即可。

 回复 引用   

#2楼[楼主] 2007-10-10 08:13 lovecherry      
@路过
的确是这样的,我比较懒,顺手就用了VS封装字段的功能。。。

 回复 引用 查看   

#3楼 2007-10-16 22:35 base[未注册用户]
用Singleton模式实现的类,跟将类的所有成员跟方法声明成Static的类是否有区别?
 回复 引用   

#4楼[楼主] 2007-10-17 09:35 lovecherry      
@base
没有实例和一个实例的区别

 回复 引用 查看   

#5楼 2007-10-17 16:51 ouyangyc[未注册用户]
一个Singleton类和一个静态类有什么不一样?
 回复 引用   

#6楼 2007-10-18 09:47 codefan[未注册用户]
构造函数为什么私有化?
 回复 引用   

#7楼 2007-10-18 14:15 arpu[未注册用户]
哥哥编译有错误
 回复 引用   

#8楼 2007-10-19 17:07 Hollydsj      
public static LoadBalanceServer GetLoadBalanceServer()
{
if (lbs == null)
{
lock (syncLock)
{
if (lbs == null)
{
Thread.Sleep(100);
lbs = new LoadBalanceServer();
}
}
}
return lbs;
}

@lovecherry 对于用双重锁定方式实现单件模式,网上褒贬不一。不知道您是怎么理解双重锁定和下面的实现方法,谁优谁劣?

public static LoadBalanceServer GetLoadBalanceServer()
{
lock (syncLock)
{
if (lbs == null)
{
Thread.Sleep(100);
lbs = new LoadBalanceServer();
}
}
return lbs;
}
还请指教!!

 回复 引用 查看   

#9楼 2007-10-25 14:35 abblly[未注册用户]
public LoadBalanceServer()

{

for (int i = 0; i < SERVER_COUNT; i++)

{

serverList.Add(new LobbyServer("LobbyServer" + i));

}

}
要改成private的吧?

 回复 引用   

#10楼[楼主] 2007-10-25 14:51 lovecherry      
@abblly
谢谢,你这样的阅读者使得BLOG的价值大增

 回复 引用 查看   

#11楼 2008-01-02 20:06 gctren[未注册用户]
你好! 我想请教一个场景, 如果说多少服务器是用配置来文件来控制的话, 如何做到我能方便的修改配置文件就能添加服务器, 而不需要进行重启系统. 这种情况下如何能很好的利用单键模式呢?
 回复 引用   

单例的实现感觉不怎么好,找了一些资料,还请赐教

http://www.yoda.arachsys.com/csharp/singleton.html

 回复 引用   

@Hollydsj
为性能考虑
if判断比lock耗时要少,lock需要内核对象.
因为只有一次需要lock进入然后实例化,以后都不会需要了,所以外部套个if速度会快些.

 回复 引用   

程序编译执行,有时OK,有时会在ShowServerInfo()中出现异常,偶用的是VS2008.说ls.PlayerList已改变,无法枚举
 回复 引用   

private static object syncLock = new object();
public void EnterPlayer(string playerName)
{
lock (syncLock)
{
playerList.Add(playerName);
}
}
改成这样就没有问题了。

 回复 引用   

提示无法枚举的错误,把这段
foreach (string player in ls.PlayerList)

{

Console.WriteLine(player);

}
改成这个就可以了
for (int i=0;i<ls.PlayerList.Count;i++)

{

Console.WriteLine(ls.PlayerList[i]);

}

 回复 引用   

#17楼 2009-08-09 14:22 yeefa[未注册用户]
我想问一下"syncLock"在这里起到一个什么作用呢?
 回复 引用   

#18楼 2009-08-09 14:29 yeefa[未注册用户]
public static LoadBalanceServer GetLoadBalanceServer()
{
if (lbs == null)
{
lock (syncLock)
{
if (lbs == null)
{
Thread.Sleep(100);
lbs = new LoadBalanceServer();
}
}
}
return lbs;
}

这里前面已经有了if (lbs == null)
,为什么还要在lock下面增加if (lbs == null)

 回复 引用   

private static volatile LoadBalanceServer lbs;
这个为什么加上volatile关键字?

 回复 引用   

@angellinby
加这个关键字才能实现单态呀

 回复 引用 查看   

#21楼 2010-06-09 16:34 指尖的奇迹      
不错,虚拟场景很容易懂。学习了。。
 回复 引用 查看   

#22楼 2010-08-15 21:30 虫子CCC      
不太明白volatile关键字的作用!C#帮助说,volatile只是指示一个字段可以由多个同时执行的线程修改。这跟单态有关系吗?
 回复 引用 查看   

#23楼 2010-10-14 11:49 向往高空      
引用Thread.Sleep(100);

这样处理不知道有何深意 , 但是加上Thread.Sleep(100);
有时会出现
================== LobbyServer0 ==================
================== LobbyServer1 ==================
================== LobbyServer2 ==================
这样的结果 但是去掉Thread.Sleep(100); 就没有这种情况出现

 回复 引用 查看   

#24楼 2010-11-17 10:52 菜鸟老了      
最后一句话不大明白:程序的可扩展不要滥用Singleton模式,只有非一个实例不可的情况下才考虑引入Singleton。否则,程序的可扩展性可能会受到限制。
谢谢

 回复 引用 查看   

#25楼 2011-07-22 14:57 web_飞杨      
@codefan
单模式就是得把构造函数私有化,防止其它地方实例化,单例模式只允许自己创建自己的唯一的实例!

 回复 引用 查看   

#26楼 2011-10-09 16:23 果果天涯      
需要注意的是在多线程环境下使用一般的懒汉单例模式时,如果多个线程同时进入判断对象是否为空的代码段时,若此时对象为空,那么多个线程就可能同时创建多个实例了。为解决这个问题,我们对GetLoadBalanceServer方法中的一段加锁,这样就能防止创建多个实例现象的发生,值得注意的是,在GetLoadBalanceServer方法中我们需要两次判断对象是否为空,这两个判断分别在临街区外和临界区内,前者是为了不至于每次调用GetLoadBalanceServer方法都要加锁,减小了系统开销;另外当有一个线程进入临界区时,其它线程也通过了对象为空的条件判断,导致最后创建了多个对象情况的发生,后者则是为了防止这种情况的发生,即所谓“双重锁定”。
 回复 引用 查看   

#27楼 2011-10-21 01:34 anncesky      
报错有时会在ShowServerInfo()中出现异常,偶用的是VS2008.说ls.PlayerList已改变,无法枚举
或者报数组长度不正确,是由于
Thread.Sleep(100);
引起的

由于在执行 LoadBalanceServer.GetLoadBalanceServer().ShowServerInfo();
过程中,处于Sleep的线程正好开始启动,修改了playerList 就会报错了

从另一方面也说明,示例代码还有不足之处

 回复 引用 查看   

#28楼 2011-10-21 01:42 anncesky      
volatile 关键字是相对于编译器优化来说的

一般的变量从内存地址中取一次,而后对这个变量存储会直接从寄存器中取,这样以提高速度
但是加上volatile 关键字都,编译器会让这个变量每次都从内存中取,以绝对保证是最新值

 回复 引用 查看   

#29楼 2012-01-05 14:13 DL_HaiTao      
public static LoadBalanceServer GetLoadBalanceServer()

{

if (lbs == null)

{

lock (syncLock)

{

if (lbs == null)

{

Thread.Sleep(100);

lbs = new LoadBalanceServer();

}

}

}

return lbs;

}

hi ,楼主 ,你的DCL貌似在多线程情况下有些问题:
1.new一个对象分很多步骤:有分配内存和给各个对象的字段赋值。
2.如果一个线程走到第二个锁的内部,并创建对象,而对象内存分配没有给字段赋值,这时恰好另一个线程走到第一个锁开始,判断对象是否为空,因为内存已经分配,对象有指向,所以返回为非空,但是对象的状态是错误的,导致各种问题。

 回复 引用 查看