设计模式之单例模式

本贴最后更新于 2903 天前,其中的信息可能已经物是人非

引言

设计模式是老生常谈的问题,有人工作多年却对设计模式一窍不通,但是更多的人是懂一点点,但是不求甚解。其实这样不好,暂且不说在工作中的应用,即便是在面试时,被面试官问到设计模式时一脸懵逼,是非常尴尬的事情。本文不废话,不谈大篇理论教学,只针对面试,给出设计模式的关键点,从应试的角度,让大家认识和理解设计模式。

首先搞清楚一点,设计模式不是高深技术,不是奇淫技巧。设计模式只是一种设计思想,针对不同的业务场景,用不同的方式去设计代码结构,其最最本质的目的是为了解耦,延伸一点的话,还有为了可扩展性和健壮性,但是这都是建立在解耦的基础之上。

我们都知道大名鼎鼎的 GoF 的 21 种设计模式,看过 head first 的这本书的人应该不少。针对这 21 种设计模式,面试官问出的问题可能千变万化,让人莫名的忧(dan)伤(teng),但是不要怕,只要你搞清楚了每种设计模式的关键点和精髓,就可以举一反三,迎刃而解了。

单例模式 1

今天我们来看看最简单的单例模式。理论我就不介绍了,没听说过的可以去查一下。即便是最简单的单例,也有关键点。举个不太恰当地例子,看过修仙修道之类小说的同学都知道,一般阵法高手做阵都有一个或多个“阵眼”,同理,我们每种设计模式也有“阵眼”,那么单例的“阵眼”在哪里?各位莫慌,我们先来看看代码。根据单例模式的理论:保证系统中只有一个实例,于是我撸了以下代码

public class Singleton {  

    private Singleton() {}                     //关键点0:构造函数是私有的

    private static Singleton single = null;    //关键点1:声明单例对象是静态的

    public static Singleton GetInstance()      //通过静态方法来构造对象
    {                        
         if (single == null) 
         {                                     //关键点2:判断单例对象是否已经被构造
             single = new Singleton();  
         }    
        return single;  
    }  
}

好了,如果我是面试官,你是候选人,我要你撸个单例给我,你撸以上代码问我资词不资词,我肯定是不资词的。为什么?真的不是因为我想搞个大新闻,而是这段单例的代码一般情况下还是可以勉强运行,但是,遇到多线程的并发条件下就大清药丸了。因为这段代码是线程不安全的,有可能给 new 出多个单例实例,都多个了,还是屁的“单例”啊。

单例模式 2

好,废话不多说,继续撸代码,你可能会说:”不是说线程不安全吗?小爷我加上线程安全判断呗,度娘在手天下我有,来了您呐~~~“

public class Singleton {  

    private Singleton() {}                     //关键点0:构造函数是私有的

    private static Singleton single = null;    //关键点1:声明单例对象是静态的

    private static object obj= new object();

    public static Singleton GetInstance()      //通过静态方法来构造对象
    {                        
         if (single == null)                   //关键点2:判断单例对象是否已经被构造
         {                             
            lock(obj)                          //关键点3:加线程锁
            {
               single = new Singleton();  
             }
         }    
        return single;  
    }  
}

好了,这回该消停了吧,锁也加了,线程也安全了。But,你还是太连清。。。这样依然有问题。问题在哪里?就在关键点 2,检测单例是否被构造。虽然这里判断了一次,但是由于某些情况下,可能有延迟加载或者缓存的原因,只有关键点 2 这一次判断,仍然不能保证系统是否只创建了一个单例,也可能出现多个实例的情况,那么怎么办呢?

单例模式 3

public class Singleton {  

    private Singleton() {}                     //关键点0:构造函数是私有的

    private static Singleton single = null;    //关键点1:声明单例对象是静态的

    private static object obj= new object();

    public static Singleton GetInstance()      //通过静态方法来构造对象
    {                        
         if (single == null)                   //关键点2:判断单例对象是否已经被构造
         {                             
            lock(obj)                          //关键点3:加线程锁
            {
               if(single == null)              //关键点4:二次判断单例是否已经被构造
               {
                  single = new Singleton();  
                }
             }
         }    

        return single;  
    }  
}

所以,在判断单例实例是否被构造时,需要检测两次,在线程锁之前判断一次,在线程锁之后判断一次,再去构造实例,这样就万无一失了。是不是很简(Tu)单(Xue)呢?

总结

最后,我们来归纳一下。下次面试别人再问你单例模式,你可以这样说:

单例是为了保证系统中只有一个实例,其关键点有 5

  1. 私有构造函数

  2. 声明静态单例对象

  3. 构造单例对象之前要加锁(lock 一个静态的 object 对象)

  4. 需要两次检测单例实例是否已经被构造,分别在锁之前和锁之后

如果要你撸代码,你就撸最后这一段,完美~~~面试官要是个女的准想和你生猴子。。。

哦,别高兴太早了,面试官要是个男的,也可能会问你下列问题

  1. 为何要检测两次?

如上面所述,有可能延迟加载或者缓存原因,造成构造多个实例,违反了单例的初衷。

  1. 构造函数能否公有化?

不行,单例类的构造函数必须私有化,单例类不能被实例化,单例实例只能静态调用

  1. lock 住的对象为什么要是 object 对象,可以是 int 吗?

不行,锁住的必须是个引用类型。如果锁值类型,每个不同的线程在声明的时候值类型变量的地址都不一样,那么上个线程锁住的东西下个线程进来会认为根本没锁,相当于每次都锁了不同的门,没有任何卵用。而引用类型的变量地址是相同的,每个线程进来判断锁多想是否被锁的时候都是判断同一个地址,相当于是锁在通一扇门,起到了锁的作用。

  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖
  • Singleton
    5 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...