设计模式学习-单例模式

饿汉模式

单例模式又被称为单件模式,这个模式作用是保持程序中只有`唯一`对象,一听到唯一,那肯定就明白了,无非就是不让别人创建新对象呗,只需要两点就可以

1.私有化构造函数, 2.创建一个静态对象属性以便外部使用

这么简单还算一种模式?别急,我们慢慢看,首先,我们创建一个单例

class Singleton
{
    //静态对象属性
    public static Singleton SingletonInstance { get; } = new Singleton();
    //私有化构造函数
    private Singleton()
    {
        Console.WriteLine("Singleton构造");
    }
}

挺简单的吗这不是,的确,挺简单的,这就是单例模式其中之一的饿汉模式,什么意思呢,

饿汉模式:在程序启动单例类被加载时,就实例化单例模式

但是这么做不感觉有问题吗?假如这个类我们并不使用或在程序启动很久以后我们才使用,那么这个对象的预创建不就很浪费吗?并且如果这个对象的创建需要很大的资源,那....,所以我们需要延迟单例对象的创建.

懒汉模式

将对象延迟到第一次访问时才创建,这种被称为`懒汉模式`

懒汉模式:当第一次访问单例对象时才去实例化对象

看起来也挺简单的样子,无非是将对象实例化放在属性的get

class Singleton
{
    private static Singleton _singleton=null;   
    //静态对象属性
    public static Singleton SingletonInstance
    {
        get
        {
            if (_singleton == null)
            {//如果对象不存在则创建
                _singleton = new Singleton();
            }
            return _singleton;
        }

    }
    //私有化构造函数
    private Singleton()
    {
        Console.WriteLine("Singleton构造");
    }
}

看起来感觉挺不错的样子,但是真是这样吗?来做一个多线程的例子看看.

static void Main(string[] args)
{
       Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;});
       Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;});
       Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;});
       Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;});
       Console.ReadKey();
 }

开4个线程进行测试

Task.Run() 是从线程池中获取线程进行使用

可以看到对于多线程来说这个单例完全无用,解决多线程的办法就是加锁,所以需要在实例化对象进行加锁

private static object objLock = new object();
//静态对象属性
public static Singleton SingletonInstance
{
     get
     {
         lock (objLock)
         {
            if (_singleton == null)
             {//如果对象不存在则创建
                _singleton = new Singleton();
              }
          }
          return _singleton;
     }
}

但是都知道,加锁会极大的影响性能,每一次都判断锁感觉上是一种极大的浪费.,然后再次优化就出现了经典的单例实例实现双重检查锁

public static Singleton SingletonInstance
{
    get
    {
        if (_singleton == null)
        {//解决锁的效率问题.
            lock (objLock)
            {
                if (_singleton == null)
                {//如果对象不存在则创建
                    _singleton = new Singleton();
                }
            }
        }
        return _singleton;
    }
}

在外面加一个if判断,这层if看起来是多余的,但是它极大的节省了性能,杜绝了大多数无多并发时锁的验证,从而提高了性能.

C#单例另一种实现---延迟加载

在C#中有一个Lazy类,这个类是一个延迟加载类,也就是自动为我们实现延迟加载功能,并且还是线程安全的,也就是说完全可以利用这个类实现单例

class SingletonLazy
{
    //Lazy需要一个无参有返的委托,
    public static  Lazy<SingletonLazy> SingletonInstance = new Lazy<SingletonLazy>(() => { return new SingletonLazy(); });
    //私有化构造函数
    private SingletonLazy()
    {
        Console.WriteLine("SingletonLazy构造");
    }
}

Lazy构造器有一个参数是传入一个无参有返的委托,整好可以实例化对象,

static void Main(string[] args)
{
    SingletonLazy singletonLazy = null;
    Console.WriteLine("延迟");
    Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; });
    Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; });
    Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; });
    Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; });
    Console.ReadKey();
}

对这个单例进行测试,测试结果与刚才无异,在工作中很多都是使用这种方式来实现单例模式

Lazy

下面来看看Lazy的实现机制,其实我们也大致能想到内部到底是如何处理的

public class Lazy<T>
{
    //内部类,存储数据的类
    private class Boxed
    {
        internal T m_value;

        internal Boxed(T value)
        {
            m_value = value;
        }
    }
    private object m_boxed;
    //委托
    private Func<T> m_valueFactory;
    //线程安全对象
    private object m_threadSafeObj = new object();
    //委托默认值
    private static readonly Func<T> ALREADY_INVOKED_SENTINEL = () => default(T);
    public Lazy(Func<T> valueFactory)
    {
        m_valueFactory = valueFactory ?? throw new ArgumentNullException("valueFactory");
    }
    //Value
    public T Value
    {
        get
        {
            Boxed boxed = null;
            if (m_boxed != null)
            {
                boxed = (m_boxed as Boxed);
                if (boxed != null)
                {
                    return boxed.m_value;
                }
                throw new Exception();
            }
            return LazyInitValue();
        }
    }
    //初始化Value值
    private T LazyInitValue()
    {
        Boxed boxed = null;
        //读取安全对象
        object obj = Volatile.Read<object>(ref m_threadSafeObj);
        bool flag = false;
        try
        {
            //如果是第一次,则开启锁并创建对象
            if (ALREADY_INVOKED_SENTINEL !=obj)
            { 
                Monitor.Enter(obj, ref flag);
            }
            if (m_boxed == null)
            {
                //创建Boxed对象并将m_threadSafeObj设置为ALREADY_INVOKED_SENTINEL
                boxed = (Boxed)(m_boxed = CreateValue());
                Volatile.Write<object>(ref m_threadSafeObj, (object)ALREADY_INVOKED_SENTINEL);
            }
            else
            {//已存在对象
                boxed = (m_boxed as Boxed);
            }
        }
        finally
        {
        if (flag)
            {
                Monitor.Exit(obj);
            }
        }
        return boxed.m_value;
    }
    //创建Boxed对象
    private Boxed CreateValue()
    {
        if (m_valueFactory != null)
        {
            try
            {
                Func<T> valueFactory = m_valueFactory;
                 if (valueFactory == ALREADY_INVOKED_SENTINEL)
                {
                    return null;
                }
                return new Boxed(valueFactory());
            }
            catch (Exception)
            {
                throw;
            }
        }
        try
        {
            //创建默认对象
            return new Boxed((T)Activator.CreateInstance(typeof(T)));
        }
        catch (MissingMethodException)
        {
            throw;
        }
    }
}

上面是简化版的Lazy源码,可以看到Lazy也只是利用了一个内部类Boxed对象缓存了数据,代码中有一点有意思的,在LazyInitValue()方法中使用了Volatile类读取数据进行加锁,volatile是保持多线程下数据同步的问题,一种简单方式可以在变量中加上volatile关键字

多个线程同时访问一个变量时,CLR(Common Language Runtime)为了效率会进行相应优化,比如“允许线程进行本地缓存”,这样就可能导致变量访问的不一致性。volatile就是为了解决这个问题;volatile修饰的变量,不允许线程进行本地缓存,每个线程的读写都直接操作在共享内存上,这就保证了变量始终具有一致性

单例模式定义

单例模式保证在系统中一个类仅有一个实例对象

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我的博客

Zend FrameWork之Zend_Db_Table笔记

根据Zend_Db_Table操作数据(也就是在models建立一个对应表的模型) 准备条件: course数据表中有cid课程号,自增,主键,cname课...

3583
来自专栏me的随笔

Redis中的数据结构与常用命令

对于Redis的介绍这里只写一句:Redis是一种基于内存的高性能非关系型数据库,它以kye-value的形式来存储数据。

1793
来自专栏应兆康的专栏

Python Web - Flask笔记5

MySQL Workbench是一款专为MySQL设计的ER/数据库建模工具。它是著名的数据库设计工具DBDesigner4的继任者。你可以用MySQL Wor...

1671
来自专栏JackieZheng

Hadoop阅读笔记(六)——洞悉Hadoop序列化机制Writable

  酒,是个好东西,前提要适量。今天参加了公司的年会,主题就是吃、喝、吹,除了那些天生话唠外,大部分人需要加点酒来作催化剂,让一个平时沉默寡言的码农也能成为一个...

2285
来自专栏IMWeb前端团队

Redux源码解析系列(四)-- combineReducers

本文作者:IMWeb 黄qiong 原文出处:IMWeb社区 未经同意,禁止转载 combindeReducer 字面意思就是用来合并reducer的...

1987
来自专栏AhDung

【C#】递归搜索指定目录下的指定项目(文件或目录)

---------------更新:201411201121---------------

1242
来自专栏JavaEdge

为什么java中用枚举实现单例模式会更好代码简洁

代码简洁 这是迄今为止最大的优点,如果你曾经在Java5之前写过单例模式代码,那么你会知道即使是使用双检锁你有时候也会返回不止一个实例对象。虽然这种问题通过...

5244
来自专栏玩转JavaEE

MongoDB文档查询操作(一)

上篇文章我们主要介绍了MongoDB的修改操作,本文我们来看看查询操作。 本文是MongoDB系列的第五篇文章,了解前面的文章有助于更好的理解本文: ---- ...

3596
来自专栏Web项目聚集地

手写一个Mybatis框架

在手写自己的Mybatis框架之前,我们先来了解一下Mybatis,它的源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,才能够更深入的理解源...

1042
来自专栏MasiMaro 的技术博文

OLEDB 静态绑定和数据转化接口

OLEDB 提供了静态绑定和动态绑定两种方式,相比动态绑定来说,静态绑定在使用上更加简单,而在灵活性上不如动态绑定,动态绑定在前面已经介绍过了,本文主要介绍OL...

1101

扫码关注云+社区

领取腾讯云代金券