提示:以下是本篇文章正文内容,下面案例可供参考

一、概述

保证在整个应用程序中,某个对象保证唯一性,此对象没有

单例应用场景

  • Windows的Task Manager(任务管理器),无法打开第二个
  • windows的Recycle Bin(回收站)
  • 网站的计数器,一般也是采用单例模式来实现计数的同步
  • 日志中的log对象是单例的,因为此对象初始化时就确定了log输出的格式,地址等等,确保唯一性
  • 数据库连接池的设计一般也是采用单例模式
  • 多线程的线程池的设计一般也是采用单例模式
  • HttpApplication 也是单位例的典型应用。所有的HttpModule都共享一个HttpApplication实例

单例实现的环境

单例实现的场景,也就是单例模式面临的考验,需要解决的一些问题。

  • 单线程环境
  • 多线程环境
  • 反射

二、单例的实现

单线程

在单线程下,单例的实现比较简单,分为懒汉式和饿汉式

饿汉式

在类加载时就初始化对象,简单但是过多的创建对象,可能会浪费内存空间

/// <summary>
/// 饿汉式
/// </summary>
public class SingleA
{
    private static readonly SingleA obj = new SingleA();
    private SingleA() { }
    public static SingleA GetInstance(){return obj;}
}

懒汉式

还算完美,懒加载,第一次用时才创建对象

/// <summary>
/// 懒汉式
/// </summary>
public class SingleB
{
    private static SingleB obj;
    private SingleB()
    {
    }

    public static SingleB GetInstance()
    {
        if (obj == null)
        {
            obj = new SingleB();
        }
        return obj;
    }
}

单线程+多线程

如果现在多了一个多线程环境的适配,该如何实现呢?主要要解决的问题时,如何确保对象的唯一性

饿汉式

靠,完美兼容,类加载可只加载一次,所以管你有没有多线程操作呢,对象就一个

懒汉式

的确需要处理

public class SingleC
{
    private volatile static SingleC obj;
    private static readonly  object lock_obj = new object();

    private SingleC(){}

    public static SingleC GetInstance()
    {
        /**
         * 第一个if判断作用:只有obj为空的时候才会有对象不唯一的风险,
         * 才有锁的必要
         */
        if(obj == null)
        {
            lock (lock_obj)
            {
                /**
                 * 第二个if判断作用:可能第一个线程走到这里时,第二个线程刚好把obj创建出来
                 */
                if (obj == null)
                {
                    /**
                     * 对象的创建过程经历的指令:
                     *      1.在内存中开辟空间
                     **     2.执行构造函数
                     *      3.把空间指向对象(对象地址)
                     *  在极端情况下,会出现指令重排,导致返回错误的对象,所以需要volatile关键字声明
                     */
                    obj = new SingleC();
                }
            }
        }
        return obj;
    }
}

单线程+多线程+反射

在这种环境下,确保对象的唯一性时,反射会对【单线程+多线程】模式下的单例造成破坏,无论饿汉式还是懒汉式,都会造成影响,下面以懒汉式为例提供解决方式。

public class SingleC
{
    private volatile static SingleC obj;
    private static readonly  object lock_obj = new object();
    private static bool newFlag = false;

    private SingleC()
    {
        ///防止多线程操作
        lock (lock_obj)
        {
            /**
             * GetInstance和反射都只能调用一次,谁先调用谁牛逼,但是第二次的反射就不被允许了
             */
            if (!newFlag) newFlag = true;
            else throw new Exception("不允许通过反射创建对象!!!");
        }
    }

    public static SingleC GetInstance()
    {
        /**
         * 第一个if判断作用:只有obj为空的时候才会有对象不唯一的风险,
         * 才有锁的必要
         */
        if(obj == null)
        {
            lock (lock_obj)
            {
                /**
                 * 第二个if判断作用:可能第一个线程走到这里时,第二个线程刚好把obj创建出来
                 */
                if (obj == null)
                {
                    /**
                     * 对象的创建过程经历的指令:
                     *      1.在内存中开辟空间
                     **     2.执行构造函数
                     *      3.把空间指向对象(对象地址)
                     *  在极端情况下,会出现指令重排,导致返回错误的对象,所以需要volatile关键字声明
                     */
                    obj = new SingleC();
                }
            }
        }
        return obj;
    }
}

但是问题仍然无法解决,因为可以反射修改newFlag的值,来多次创建对象,非常无语

三、其他相关

关于枚举

c#的枚举,无法像Java一样实现线程安全、反射安全的单例,它真的只是枚举

饿汉式的优化

饿汉式的优点非常明显,利用了类加载的机制去实现,而缺点就是加载的太快了,类加载了,可能并不需要去加载对象,但是对象也被加载了,内存就被可能用不到的对象给浪费了一点。
利用静态内部类

public class SingleD
{
    private SingleD()
    {

    }

    private static class Inner
    {
        //外部类加载时,成员变量还没加载,非常完美
        public static readonly SingleD obj=new SingleD();
    }

    public static SingleD GetInstance()
    {
        return Inner.obj;
    }
}

最佳实践

真的有最佳实践?并没有,反射功能的强大,几乎让人防不胜防,所以我们需要进行一些内部的约定,不允许对单例类进行反射使用。那么这种情况下只需要考虑多线程的问题,那么最佳实践就是【饿汉式的优化】,它更简洁

更多推荐