【设计模式】C#实现单例模式

简介: 单例模式:某一个类在系统中只需要有一个实例对象,而且对象是由这个类自行实例化并提供给系统其它地方使用。单例模式属于一种创建型设计模式。

【设计模式】单例模式

1、概述

单例模式:某一个类在系统中只需要有一个实例对象,而且对象是由这个类自行实例化并提供给系统其它地方使用。单例模式属于一种创建型设计模式。从概述中,我们可以总结三个要点:

  • 单例类只能有一个实例,即使是多线程运行环境下;
  • 单例类的实例一定是单例类自身创建,而不是在单例类外部用其它方式如new方式创建;
  • 单例类需要提供一个方法向整个系统提供这个实例对象。

2、单例模式实现思路

首先,我们先创建一个简单的类。

public class SingletonFirst
    {
        public SingletonFirst()
        {
            Console.WriteLine("构造函数被调用一次!!!");
        }
    }
static void Main(string[] args)
        {
            SingletonFirst singleton1 = new SingletonFirst();
            SingletonFirst singleton2 = new SingletonFirst();
            Console.WriteLine($"判断实例地址是否一致? {singleton1.Equals(singleton2)}");
        }

此时,输出结果是False。

下面我们构造一个单例类:

  • 构造函数定义为私有,防止外部创建实例;
  • 提供一个方法向整个系统提供这个实例对象。
public class SingletonFirst
    {
        /// <summary>
        /// 构造函数定义为私有,防止外部创建实例;
        /// </summary>
        private SingletonFirst()
        {
            Console.WriteLine("构造函数被调用一次!!!");
        }

        private static SingletonFirst _Instance = null;

        /// <summary>
        /// 提供一个方法向整个系统提供这个实例对象
        /// </summary>
        /// <returns></returns>
        public static SingletonFirst CreateSingleton()
        {
            if (_Instance == null)
                _Instance = new SingletonFirst();
            return _Instance;
        }

    }
static void Main(string[] args)
        {
            SingletonFirst singleton1 = SingletonFirst.CreateSingleton();
            SingletonFirst singleton2 = SingletonFirst.CreateSingleton();
            Console.WriteLine($"判断实例地址是否一致? {singleton1.Equals(singleton2)}");
        }

此时,输出结果是True。

但是上面的写法是线程不安全的。单例模式需要注意的地方就是要写出一个能保证在多线程环境下也能保证实例唯一性的单例

这里,我们改造一下类,使用多线程查看一下输出的结果。

public class SingletonFirst
    {
        /// <summary>
        /// 构造函数定义为私有,防止外部创建实例;
        /// </summary>
        private SingletonFirst()
        {
            int result = 0;
            for (int i = 0; i < 10000; i++)
            {
                result += i;
            }
            Thread.Sleep(2000);
            Console.WriteLine("构造函数被调用一次!!!");
        }

        private static SingletonFirst _Instance = null;

        /// <summary>
        /// 提供一个方法向整个系统提供这个实例对象
        /// </summary>
        /// <returns></returns>
        public static SingletonFirst CreateSingleton()
        {
            if (_Instance == null)
                _Instance = new SingletonFirst();
            return _Instance;
        }

    }
static void Main(string[] args)
        {
            //SingletonFirst singleton1 = SingletonFirst.CreateSingleton();
            //SingletonFirst singleton2 = SingletonFirst.CreateSingleton();

            SingletonFirst singleton1 = null;
            SingletonFirst singleton2 = null;

            Task.Run(() =>
            {
                singleton1 = SingletonFirst.CreateSingleton();
            });

            Task.Run(() =>
            {
                singleton2 = SingletonFirst.CreateSingleton();
            });
            Thread.Sleep(3000);
            Console.WriteLine($"判断实例地址是否一致? {singleton1.Equals(singleton2)}");
        }

此时,输出结果是False。

线程不安全解决方法:

给线程加锁,就可以解决上述问题。

public class SingletonFirst
    {
        /// <summary>
        /// 构造函数定义为私有,防止外部创建实例;
        /// </summary>
        private SingletonFirst()
        {
            int result = 0;
            for (int i = 0; i < 10000; i++)
            {
                result += i;
            }
            Thread.Sleep(2000);
            Console.WriteLine("构造函数被调用一次!!!");
        }

        private static SingletonFirst _Instance = null;
        private static readonly object singletonFirstLock = new object();

        /// <summary>
        /// 提供一个方法向整个系统提供这个实例对象
        /// </summary>
        /// <returns></returns>
        public static SingletonFirst CreateSingleton()
        {
            lock (singletonFirstLock)
            {
                if (_Instance == null)
                    _Instance = new SingletonFirst();
            }
            return _Instance;
        }

    }

此时,输出结果是True。

这样一来,确实线程安全了,但是又带来了另一个问题:程序的性能极大的降低了,高并发下多个线程去获取这个实例,现在却要排队。

那么有没有一种写法,可以同时兼顾到效率和线程安全两方面,这个就是我们下面将要介绍的double-check的方式。

double-check:
public class SingletonFirst
    {
        /// <summary>
        /// 构造函数定义为私有,防止外部创建实例;
        /// </summary>
        private SingletonFirst()
        {
            int result = 0;
            for (int i = 0; i < 10000; i++)
            {
                result += i;
            }
            Thread.Sleep(2000);
            Console.WriteLine("构造函数被调用一次!!!");
        }

        private static SingletonFirst _Instance = null;
        private static readonly object singletonFirstLock = new object();

        /// <summary>
        /// 提供一个方法向整个系统提供这个实例对象
        /// </summary>
        /// <returns></returns>
        public static SingletonFirst CreateSingleton()
        {
            if (_Instance == null)
            {
                lock (singletonFirstLock)
                {
                    if (_Instance == null)
                        _Instance = new SingletonFirst();
                }
            }
            return _Instance;
        }

    }

这种单例的写法做了两次 if (_Instance == null)的判断,因此被称为double-check的方式。

  • 第一次check为了提高访问性能。因为一旦实例被创建,后面线程的所有的check都为假,不需要执行锁了。
  • 第二次check是为了线程安全,确保多线程环境下只生成一个实例。

3、懒汉模式和饿汉模式

懒汉模式

上述介绍的模式就是懒汉模式,顾名思义,这个类很懒,只要别人不找它要实例,它都懒得创建。

饿汉模式

在初始化时,我们就创建了唯一的实例,即便这个实例后面并不会被使用。

下面我们介绍饿汉模式两种实现方式。

静态构造函数
public class SingletonThird
    {
        /// <summary>
        /// 构造函数定义为私有,防止外部创建实例;
        /// </summary>
        private SingletonThird()
        {
            int result = 0;
            for (int i = 0; i < 10000; i++)
            {
                result += i;
            }
            Thread.Sleep(2000);
            Console.WriteLine("构造函数被调用一次!!!");
        }

        private static SingletonThird _Instance = null;

        /// <summary>
        /// 静态构造函数,在程序第一次使用这个类型之前,由CLR调用且只调用一次,适合做初始化
        /// </summary>
        static SingletonThird()
        {
            _Instance = new SingletonThird();
        }

        /// <summary>
        /// 提供一个方法向整个系统提供这个实例对象
        /// </summary>
        /// <returns></returns>
        public static SingletonThird CreateSingleton()
        {
            return _Instance;
        }

    }
静态字段
public class SingletonSecond
    {
        /// <summary>
        /// 构造函数定义为私有,防止外部创建实例;
        /// </summary>
        private SingletonSecond()
        {
            int result = 0;
            for (int i = 0; i < 10000; i++)
            {
                result += i;
            }
            Thread.Sleep(2000);
            Console.WriteLine("构造函数被调用一次!!!");
        }

        /// <summary>
        /// 静态字段,在程序第一次使用这个类型之前,由CLR调用且只调用一次,适合做初始化
        /// </summary>
        private static SingletonSecond _Instance = new SingletonSecond();

        /// <summary>
        /// 提供一个方法向整个系统提供这个实例对象
        /// </summary>
        /// <returns></returns>
        public static SingletonSecond CreateSingleton()
        {
            return _Instance;
        }

    }
相关文章
|
1月前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
61 0
|
1月前
|
设计模式 缓存 安全
【设计模式】单例模式:确保类只有一个实例
【设计模式】单例模式:确保类只有一个实例
23 0
|
3月前
|
设计模式 数据库连接 数据库
发挥设计模式单例模式的力量:从技术到社会的转变
发挥设计模式单例模式的力量:从技术到社会的转变
|
3月前
|
设计模式 安全 Java
设计模式-单例模式
设计模式-单例模式
38 0
|
1月前
|
设计模式 安全 Java
设计模式之单例模式
设计模式之单例模式
|
3月前
|
设计模式 缓存 安全
设计模式 - 创建型模式_ 单例模式 Singleton Pattern
设计模式 - 创建型模式_ 单例模式 Singleton Pattern
39 0
|
11天前
|
设计模式 存储 Java
Java设计模式:解释一下单例模式(Singleton Pattern)。
`Singleton Pattern`是Java中的创建型设计模式,确保类只有一个实例并提供全局访问点。它通过私有化构造函数,用静态方法返回唯一的实例。类内静态变量存储此实例,对外仅通过静态方法访问。
16 1
|
1月前
|
设计模式 存储 缓存
设计模式之单例模式(C++)
设计模式之单例模式(C++)
22 2
|
1月前
|
设计模式 安全 Java
Java设计模式之单例模式
在软件工程中,单例模式是一种常用的设计模式,其核心目标是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。Java作为一门广泛使用的编程语言,实现单例模式是面试和实际开发中的常见需求。
66 9
Java设计模式之单例模式
|
2月前
|
设计模式 存储 安全
【设计模式】创建型模式之单例模式(Golang实现)
【2月更文挑战第3天】一个类只允许创建一个对象或实例,而且自行实例化并向整个系统提供该实例,这个类就是一个单例类,它提供全局访问的方法。这种设计模式叫单例设计模式,简称单例模式。
34 1