深刻理解设计模式(一):单例模式

本文首先概述了单例模式,揭示了单例模式的应用场景和优缺点,最后咱们给出了单例模式的几种实现方式及注意事项。web

1、什么是单例模式

单例模式是一种经常使用的软件设计模式,其定义是单例对象的类只能容许一个实例存在。数据库

许多时候整个系统只须要拥有一个的全局对象,这样有利于咱们协调系统总体的行为。好比在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,而后服务进程中的其余对象再经过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。windows

 

单例的实现主要是经过如下两个步骤设计模式

  1. 将该类的构造方法定义为私有方法,这样其余处的代码就没法经过调用该类的构造方法来实例化该类的对象,只有经过该类提供的静态方法来获得该类的惟一实例;
  2. 在该类内提供一个静态方法,当咱们调用这个方法时,若是类持有的引用不为空就返回这个引用,若是类保持的引用为空就建立该类的实例并将实例的引用赋予该类保持的引用。

2、单例模式的应用场景

  举一个小例子,在咱们的windows桌面上,咱们打开了一个回收站,当咱们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程当中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。安全

  继续说回收站,咱们在实际使用中并不存在须要同时打开两个回收站窗口的必要性。假如我每次建立回收站时都须要消耗大量的资源,而每一个回收站之间资源是共享的,那么在没有必要屡次重复建立该实例的状况下,建立了多个实例,这样作就会给系统形成没必要要的负担,形成资源浪费。服务器

  再举一个例子,网站的计数器,通常也是采用单例模式实现,若是你存在多个计数器,每个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。可是若是采用单例模式实现就不会存在这样的问题,并且还能够避免线程安全问题。一样多线程的线程池的设计通常也是采用单例模式,这是因为线程池须要方便对池中的线程进行控制多线程

  一样,对于一些应用程序的日志应用,或者web开发中读取配置文件都适合使用单例模式,如HttpApplication 就是单例的典型应用。性能

  从上述的例子中咱们能够总结出适合使用单例模式的场景和优缺点:  网站

   适用场景: spa

  • 1.须要生成惟一序列的环境
  • 2.须要频繁实例化而后销毁的对象。
  • 3.建立对象时耗时过多或者耗资源过多,但又常常用到的对象。 
  • 4.方便资源相互通讯的环境

3、单例模式的优缺点

优势

  • 在内存中只有一个对象,节省内存空间;

  • 避免频繁的建立销毁对象,能够提升性能;

  • 避免对共享资源的多重占用,简化访问;

  • 为整个系统提供一个全局访问点。

缺点

  •  不适用于变化频繁的对象;

  • 滥用单例将带来一些负面问题,如为了节省资源将数据库链接池对象设计为的单例类,可能会致使共享链接池对象的程序过多而出现链接池溢出;

  • 若是实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会致使对象状态的丢失;

4、单例模式的实现

1.饿汉式

// 饿汉式单例
public class Singleton1 {
 
    // 指向本身实例的私有静态引用,主动建立
    private static Singleton1 singleton1 = new Singleton1();
 
    // 私有的构造方法
    private Singleton1(){}
 
    // 以本身实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton1 getSingleton1(){
        return singleton1;
    }
}

咱们知道,类加载的方式是按需加载,且加载一次。。所以,在上述单例类被加载时,就会实例化一个对象并交给本身的引用,供系统使用;并且,因为这个类在整个生命周期中只会被加载一次,所以只会建立一个实例,即可以充分保证单例。

 

优势:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。若是从始至终从未使用过这个实例,则会形成内存的浪费。

2.懒汉式

// 懒汉式单例
public class Singleton2 {
 
    // 指向本身实例的私有静态引用
    private static Singleton2 singleton2;
 
    // 私有的构造方法
    private Singleton2(){}
 
    // 以本身实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton2 getSingleton2(){
        // 被动建立,在真正须要使用时才去建立
        if (singleton2 == null) {
            singleton2 = new Singleton2();
        }
        return singleton2;
    }
}

咱们从懒汉式单例能够看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给本身的引用。

这种写法起到了Lazy Loading的效果,可是只能在单线程下使用。若是在多线程下,一个线程进入了if (singleton == null)判断语句块,还将来得及往下执行,另外一个线程也经过了这个判断语句,这时便会产生多个实例。因此在多线程环境下不可以使用这种方式。

3.双重加锁机制

public class Singleton
    {
        private static Singleton instance;
        //程序运行时建立一个静态只读的进程辅助对象
        private static readonly object syncRoot = new object();
        private Singleton() { }
        public static Singleton GetInstance()
        {
            //先判断是否存在,不存在再加锁处理
            if (instance == null)
            {
                //在同一个时刻加了锁的那部分程序只有一个线程能够进入
                lock (syncRoot)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

Double-Check概念对于多线程开发者来讲不会陌生,如代码中所示,咱们进行了两次if (singleton == null)检查,这样就能够保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。

使用双重检测同步延迟加载去建立单例的作法是一个很是优秀的作法,其不但保证了单例,并且切实提升了程序运行效率

优势:线程安全;延迟加载;效率较高。

4.静态初始化

//阻止发生派生,而派生可能会增长实例
    public sealed class Singleton
    {
        //在第一次引用类的任何成员时建立实例,公共语言运行库负责处理变量初始化
        private static readonly Singleton instance=new Singleton();
        
        private Singleton() { }
        public static Singleton GetInstance()
        {
            return instance;
        }
    }

在实际应用中,C#与公共语言运行库也提供了一种“静态初始化”方法,这种方法不须要开发人员显式地编写线程安全代码,便可解决多线程环境下它是不安全的问题。

5、总结

固然,单例模式的实现方法还有不少。可是,这四种是比较经典的实现,也是咱们应该掌握的几种实现方式。

从这四种实现中,咱们能够总结出,要想实现效率高的线程安全的单例,咱们必须注意如下两点:

  • 尽可能减小同步块的做用域;

  • 尽可能使用细粒度的锁。

相关文章
相关标签/搜索