为了让博客看起来不那么深入,我觉得可以让加入一点故事情节~ 锻炼一下以后写不动代码改写小说的能力~
最近准备找工作,这不今天就有家喊我去面试的;我一大早的就赶到了公司;
此处省略1万字跟面试官的客套话,直接进入正题;
面试官:小胡,你知道哪些设计模式阿?
我说:设计模式了解得不多,只知道单例模式跟工厂模式,装饰模式,适配器模式,享元模式,观察者模式;
面试官:哟,知道得还挺多的啊,行,先手写一个单例模式来看看;
自信的我迅速的在纸上写上了代码;还不忘加上注释,以体现出自己的代码规范;
//饿汉式
public class Singleton01 {
//1.将构造方法私有化;不允许外部直接创建对象;
private Singleton01(){
}
//2.利用static关键字创建类的唯一实例;依然用private修饰
private static Singleton01 singleton = new Singleton01();
//3.对外公开提供一个获取实例的方法,使用 public static修饰
public static Singleton01 getInstance(){
return singleton;
}
}
面试官一看,心想还行,不是个浑水摸鱼的,那试探一下,说:那你再写一个懒汉式给我看看;
我:心想这还不简单,饿汉懒汉不都是两个痴汉么?我又迅速的写完了懒汉式;
//懒汉式
public class Singleton02 {
//1.将构造方法私有化,还是不允许外部直接用new的方式产生对象
private Singleton02(){
}
//2.还是使用static关键字,不过这次只是声明一个类的唯一实例而已,我们不急着创建对象~
private static Singleton02 singleton02;
//3.对外公开提供一个获取实例的方法,使用 public static修饰
public static Singleton02 getInstance(){
if(singleton02==null){
singleton02 = new Singleton02();
}
return singleton02;
}
}
面试官:还不错,代码也挺规范的;
我:此时还挺陶醉的,心想面试也太容易了吧。哈哈~
还没容我乐够三秒,面试官又发话了;
面试官:你看看你写的饿汉式的单例模式,能说说这段代码在什么情况下会出现bug么?
我:还沉醉其中的我,突然慌了... 面试前背的单例模式都是网上找的模板阿,怎么会有bug呢? 我去,我哪知道有什么bug啊。。。 该死的百度,太不靠谱了,此时的我,也没太多的心情去黑百度了;
只能硬着头皮看着自己写的代码,首先私有化构造方法,不让外部直接调用这肯定是没错的;
第二步,使用static关键字保证Singleton02对象在 Singleton02这个类被加载一次已确保会是单例;
第三步,对外提供能够获取实例的方法,先判断Singleton02这个对象存不存在,不存在就创建Singleton02对象,存在就不创建,提升程序运行速度,这也没问题啊,返回值,修饰符都没问题,问题在哪呢? 作为菜鸟的我此时已瑟瑟发抖; 心想面试官不是诈我的吧。
我说:面试官,你好,我检查了一下,貌似没有问题;
面试官:我想,这大概就是你们初级程序员不够稳的地方吧。你仔细看看你懒汉式的第三步的判断,在多线程的情况下;会发生意外!
说个最简单的例子,如果把singleton02比作是一个妹子。你这段代码定的规矩是:如果该妹子为单身,你只要买套房子( new Singleton02() ),然后把这套房子写上妹子的名字,这个妹子就是你女朋友了; 但是,现实情况远远没有你想的那么简单! 你想要这个妹子,别人也想要这个妹子呢!
你看到这个妹子是单身,于是你就去买了一套房子,正准备开开心心的在房产证写上妹子的名字,期待佳人的时候,突然发现妹子已经名花有主了,什么情况?A同学在你看到妹子是单身的时候,他也想追妹子,他跟你一起去小区看的房子,一起付的款,但是A比你先去找房地产公司,先你一步在房产证上写了妹子的名字,妹子是A的了,当然你还是可以写妹子的名字,这样妹子就有两套房子了,但是你女朋友就跟别人跑了。。
此刻的我,恍然大悟,单例模式的初衷是 保证在整个应用程序中某个实例对象有且只会有一个。写饿汉式的时候面试官没有找我的茬是因为第二步对象的创建加了static关键字,在类加载的时候就已经是 加载且只加载一次 ; 所以不会出现线程安全的问题,而懒汉式,第二步只是声明了一个对象而已,并没有创建,创建对象的时候又没有加锁进行同步,也就意味着所有线程只要通过了 if 的那个判断就会去创建对象,如果A线程进入了 if 的判断, 只要new Singleton02()对象没有跟 singleton02 发生引用(看理解为 句柄 或 “指针”关系),那么 singleton02 就还是为null, 此时如果又有B线程进来了,他也会 去new Singleton02()然后赋值给singleton02,此时,就算A线程先进来,女朋友也没了,谁让B线程动作更快呢!
此时的我后悔不已,这么简单的问题怎么就没看出来呢......
面试官看我似乎是因为紧张了没看出来这个问题,于是问我:现在既然你知道会出现线程安全的问题,那么改怎么解决呢?
筐瓢的我,此时已经不能失误了,仔细回忆起脑袋里关系线程安全的知识,加锁,对加锁可以保证线程安全,怎么加?加在哪儿?