来次面试吧?准备好没,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:
- public class Singleton{
- // 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:
- public class Singleton{
- 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. 最好的单例,单元素的枚举类型
呵呵,其实,就这些,本来想先写工厂的,因为去面试,遇到某些对单例了解比较浅,解释起来费劲,于是先以单例开篇,请关注下篇工厂模式。