65.9K
CodeProject 正在变化。 阅读更多。
Home

设计模式系列:单例模式和多例模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (14投票s)

2017年3月26日

CPOL

4分钟阅读

viewsIcon

36173

类设计,确保只创建一个实例,并提供全局访问该实例的机制。

什么是单例模式?

单例模式是一种类的设计方式,它限制了在类外部创建对象,同时又提供了一种在调用代码请求时返回其实例的方法。任何请求都会返回同一个对象。

必备组件

单例类应该只有一个实例,不能创建多个实例。

可以通过将所有构造函数设为私有来实现这一点,这样就无法在类外部创建新实例(例如,无法使用 LogManager log=new LogManager())。

单例类应该提供一个全局访问其实例的入口点。

这可以通过创建一个公共静态方法来实现,该方法应返回类的实例引用。调用代码将是 LogManager logObj=LogManager.GetInstance()。请注意,客户端代码没有使用“new”关键字,而是使用了 LogManager 类的公共静态方法“GetInstance”。

用途

它节省了内存,因为对象不会在每次请求时创建,因为调用代码没有使用“new”关键字来为此类创建实例。

它可以用于日志记录、缓存、线程池、配置设置等。

不同版本

单例模式有许多版本,因此根据您的需求明智地选择是实现单例模式的关键。通常,单例类只有一个实例。可以有任意数量的对象引用,但类只有一个实例。下图说明了单例的基本思想。

标准单线程环境

在这种情况下,应用程序或系统运行在单个线程上,您可以非常确定不会有多个线程访问单例类。

public class Singleton
{
    private static Singleton instance;
    private Singleton() { }
    public static Singleton GetInstance()
    {
        if (instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}

程序解释

概念解释

描述性解释

静态成员

由于是静态的,它只分配一次内存,并包含单例类本身的实例。

私有静态

它应该在类内部初始化和使用。

私有构造函数

它限制了从外部创建对象。

静态工厂方法

它提供了对单例对象的全局访问入口点,并返回单例类的实例。当调用此方法时,它会检查单例类的实例是否可用。如果不可用,它会创建一个实例。否则,它将返回类的同一个已创建对象引用。

懒惰实例化

对象创建操作被推迟到调用代码首次调用该方法时进行。这种推迟的方法称为懒惰实例化。当提高性能、减少数据库命中次数、优化内存使用和最小化巨大计算是设计或开发系统的主要考虑因素时,通常会使用它。

缺点

这种方法存在一些权衡。只要我们确定只有一个线程访问单例对象,我们就没有问题。上述方法就足够了。但在多线程环境中,有可能多个线程可以访问同一行代码。问题就来了。

它对于多线程环境不安全。如果两个线程同时进入“if condition”if (instance == null),那么将创建单例的两个实例。

多线程环境:线程安全

方法 1:提前实例化

“readonly”关键字强制对象可以在声明部分或构造函数级别进行初始化。这使得提前实例化成为可能。因此,当类首次编译并加载到 AppDomain 时,单例的副本就会在堆内存中创建,因为单例 **instance** 被声明为“**static**”,并在声明本身使用 **new Singleton()** 分配了对象引用。

方法 2:双重检查锁定,懒惰实例化

“lock”关键字是主要因素,它会锁定代码以阻止其他线程进入,直到当前线程完成执行并离开锁定块。在 Java 中,他们称之为“**synchronized**”。volatile 关键字确保实例变量的赋值在实例变量可以被访问之前完成。这是可选的,它提供了一些额外的保证。

实际示例

using System.IO;

using System.Text;

namespace Utility
{
    public class Logger
    {
        private FileStream fStream;
        private StreamWriter sWriter;
        private static readonly Logger _instance = new Logger();
        public static Logger Instance
        {
            get
            {
                return _instance;
            }
        }
        private Logger()
        {
            fStream = File.OpenWrite(@"c:\logs\log.txt");
            sWriter = new StreamWriter(fStream);
        }
        public void LogMessage(string msg)
        {
            sWriter.WriteLine(msg + " logged at " + DateTime.UtcNow.ToString());
            sWriter.Flush();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            string logMsg = "testing";
            Console.WriteLine("log message :{0}", logMsg);
            Logger.Instance.LogMessage(logMsg);
            Console.WriteLine("Message logged successfully");
            Console.ReadKey();
        }
    }
}

输出

 

多例模式

多例模式是单例模式的扩展和增强版本。正如模式的名称所示,多例模式只不过是类实例的预定义“**n**”个集合,而单例类只有一个实例。多例模式(类)使用 Hash 或 Dictionary 来分组实例列表。列表中的每个实例都与相应的键配对。通过使用键,将相应的实例返回给调用代码。
 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Utility
{
    public sealed class  BackendServer
    {

        private static readonly Dictionary<int, BackendServer> serverPool = 
        new Dictionary<int, BackendServer>
        {
            {1,new BackendServer() { ServerName="Server 1", IPAddress="121.121.121.121" } },
            {2,new BackendServer() { ServerName="Server 2", IPAddress="121.125.129.122" } },
            {3,new BackendServer() { ServerName="Server 3", IPAddress="121.131.121.123" } }
        };
        private static readonly object _lock = new object();
        string ServerName { get; set; }
        string IPAddress { get; set; }

        public void Display()
        {
            Console.WriteLine("Request received by backend server{0}", ServerName);
        }
        private static readonly Random random = new Random();
        private static readonly object randomLock = new object();
        public static BackendServer GetAvailableBackendServer()
        {
            lock (randomLock)
            {
                int key = random.Next(1,(serverPool.Count+1));
                return serverPool[key];
            }
            
        }
    }
    class LoadBalancer
    {
        BackendServer ConnectToAvailableServer()
        {
            return BackendServer.GetAvailableBackendServer();
        }
        public void SericeRequest()
        {
            BackendServer instance=ConnectToAvailableServer();
            instance.Display();
        }
    }

    class ClientProgram
    {
        static void Main(string[] args)
        {
            LoadBalancer reqObj = new LoadBalancer();
            for(int i=1;i<=10;i++)
            reqObj.SericeRequest();
            Console.ReadKey();
        }
    }

}

输出

 

 

© . All rights reserved.