天天看点

我是架构师--设计模式-单例模式

来次面试吧?准备好没,GO!

   问:自我介绍下吧。(开个玩笑。。。往下)请问你用过单例模式吗?什么是单例?  

答:用过啊,单例模式就是只创建一个实例。  

   问:噢?那是单线程,还是多线程下都是呢?  

答:这个类在JVM里就一个实例(这样回答也许会更好)  

   (注:通常面试官,到这里,就会让你写一个单例,当然我们不,你看这篇文章,就是不让你再网上找其他重复的资料了。)  

   问:说说你用的场景吧  

答:........  

   问:说说单例的几种类型?或者说何时对实例的初始化?  

   问:画过单例的类图吗?会画吗?  

答:(需要吗?)  

   问:写个单例的实现吧?  

 类名:SingleTon

 实例:uniqueInstance 简称ust吧。(这里我插入一句编程规范:起名不要吝惜把实例代表的意思表达清楚,名字可稍微长点,这里就是想偷懒) 

实现1:

public class Singleton{  

  private final static Singleton ust = new Singleton();  

  private Singleton() {}  

  public static Singleton getInstance(){  

     return ust;  

  }  

}  

注:这里顺便说明一下语法。通常final与static同时出现,习惯让final在前

分析: 

1. 这里需要加final吗? 是的,因为java反射,可以改变private描述的变量

2. 有书里把这种方式称为饿汉单例模式(另一个种叫懒汉单例),并且已经为人所接受

3. 在多线程方面表现出了他优势,不需要担心方法重复里延时创建带来的原子性(这样说难理解,其实就是出现两个或多个实例)

4. 这个,不符合我们习惯的 用到时再实例化的原则(不过这没关系。。。)

 看,其实你发现这还是个不错的单例,那么其实有个最好的实现,最好的实现:

单元素的枚举类型已经成为Singleton的最佳实践

即使面对复杂的序列化或者反射攻击,绝对防止多次实例化,还有他的简洁和优雅。

 ----好吧,面试结束了。 其实你对单例的理解还是不错的,而且你已经得到了最好的答案,有兴趣彻底玩转单例吗,继续听我唠叨。

    我们看看懒汉单例模式:

实现2:

    // private final static Singleton ust = null;

// 不该加final,这里明显有偷懒嫌疑,复制上面的例子,又测试不够,以后尽量避免类似问题。

// 8月15日修正

private static Singleton ust = null;

     private Singleton(){}  

     public staic Singleton getInstance(){  

//建议null==ust的方式,能帮助更快的发现错误。

//部分老程序员的习惯,其实许多IDE会发现些低级错误。  

        if(ust==null){//A  

             ust = new Singleton();//B  

         }  

       return ust;  

     }  

}   

分析:

1. 懒汉模式,做到了需要时创建实例

2. 他遇到了尴尬的问题,因为当两个线程分开运行到A,然后进入了if块,可能就创建了2个实例,草稿的是,你已经初始了一些数据。 

改进一下:

实现3: 

private volatile static Singleton ust;  

private Singleton(){}  

public staic Singleton getInstance(){  

synchronized(Singleton.class){  

   if(ust==null){  

    ust= new Singleton();  

   }  

 }  

return ust;  

 分析:

  1. 如果你不理解synchronized 的位置,就不用单例模式这么多写法,不如学习基础

  2. volatile 确保ust被实例化后,多个线程正确处理。他失去了JVM必要的代码优化,如果不是多线程,就不要用

   3. 这个叫做 “双检查加锁”,单例最后一种方式

综合讨论会: 

小明: 单例目前一共谈到懒汉和饿汉两种,还有双检查加锁,最好的应该是单元素的枚举类型  

小刚: 是的,回答了开头说的几种单例,那么哪些场景应该用单例呢?  

小明: 我知道,有线程池,缓存,处理偏好设置,注册表,日志对象等等  

小刚: 对,我对java比较了解,我知道Runtime.getRuntime()。   

C(为吗我叫C):我知道有java.lang.reflect.Proxy类   

小明:有什么共同点呢,为什么用?是遵循对象尽量少创建原则? 

C:这是什么意思?   

小刚:这很简单,不过这说法有点问题。因为对象占内存,有要造成垃圾回收,GC的时候JVM可是只干这个麻烦事  

小明:是啊! 

小刚:我想我知道,某些对象最好只有一个实例,多了会有问题产生。   

C:什么问题?   

小刚:比如缓存,你从哪个实例里拿缓存呢?   

小明:是的啊。。。  

结束讨论会,总结一下吧。不,等等,还要补充两句: 

1. 单例模式定义:确保一个只有一个实例,并提供一个全局访问点

2. 如果getInstance()方法对应用程序不会额外负担,或者说影响不大,那写成怎样,其实没太大所谓。但是如果频繁运行,就要仔细考虑,因为一个同步,可能使得执行效率下降100倍

继续总结,还差个UML图呢,不妨在上个枚举的例子吧,枚举构造器默认私有吗? 

枚举就算了,是不是默认构造器,自己研究下吧。。。呵呵

这回真总结了: 

1. 单例,有懒汉,饿汉,双检查加锁3种常见用法 

2. 单例模式,是因为如果多了,会造成数据遗漏等麻烦 

3. 最好的单例,单元素的枚举类型

 呵呵,其实,就这些,本来想先写工厂的,因为去面试,遇到某些对单例了解比较浅,解释起来费劲,于是先以单例开篇,请关注下篇工厂模式。

本文转自 wws5201985 51CTO博客,原文链接:http://blog.51cto.com/yjplxq/931861,如需转载请自行联系原作者