【设计模式】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;
        }

    }
相关文章
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
30 2
|
11天前
|
设计模式 存储 前端开发
前端必须掌握的设计模式——单例模式
单例模式是一种简单的创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。适用于窗口对象、登录弹窗等场景,优点包括易于维护、访问和低消耗,但也有安全隐患、可能形成巨石对象及扩展性差等缺点。文中展示了JavaScript和TypeScript的实现方法。
|
16天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
21 2
|
1月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
36 4
|
1月前
|
设计模式 安全 C#
C# 单例模式的多种实现
C# 单例模式的多种实现
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
22天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
1月前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
20 1
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
27 0
|
2月前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入解析与实践
在PHP开发中,设计模式是提高代码可维护性、扩展性和复用性的关键技术之一。本文将通过探讨单例模式,一种最常用的设计模式,来揭示其在PHP中的应用及优势。单例模式确保一个类仅有一个实例,并提供一个全局访问点。通过实际案例,我们将展示如何在PHP项目中有效实现单例模式,以及如何利用这一模式优化资源配置和管理。无论是PHP初学者还是经验丰富的开发者,都能从本文中获得有价值的见解和技巧,进而提升自己的编程实践。