从这篇开始我的设计模式学习之旅,以前面对设计模式总觉得是在看天书。果然,生活就是最好的老师,当在社会上摸爬滚打过一番之后,别管多久,都会多少有点收获,这恐怕就是生活的意义吧。闲话不多说,马上入正题:在入正题之前再啰嗦一句,由于本人最熟悉的是java,所以所有的模式几乎都是以java为例的。好!!马上正题:
单例模式
这个模式估计是所有的入坑选手第一个接触到的模式,也是听到最多的一个模式,对于每一个模式几乎都是三步走:是什么(该模式的定义),为什么(使用该模式的原因)以及怎么做(如何去使用,什么时候使用该模式)。所以先来第一步:
单例模式?什么东西?
什么叫模式?你可以通俗的理解为“套路”,那什么是单例?也就是“单一实例”。学java的都知道,一个类(class),是可以创建(new)出来很多对象(也就是实例)的。比如:
public class Person{
String name;
public Person(String name){
this.name = name;
}
}
这是一个“人”类,通过这个类你可以创建很多人,什么张三,什么李四啊,阿猫阿狗啊等等:
Person zhangsan = new Person("章三");
Person lisi = new Person("李四");
这样对于Person
这个类来说就不是单一实例了。<ber>
所以单例模式就是:有没有一种套路可以让我一个类只创建出来一个实例?
可是
我们为什么要单例模式呢?
想象一下,现实中有没有什么是只有一个的,当然哦,不是说全世界只有一个,而是在一个“系统”中只有一个,比如说在一部电脑里面电脑系统只有一个任务管理器,再比如说,在金庸先生的武侠世界里只有一个武林盟主。等等等等。可以看的出来,“单一”的一个好处就是:方便管理。我再举一个代码的例子吧:
现在有一个放钥匙的钥匙包:
public class KeyCase{
private Map<String,Key> cases = new HashMap();
//code ....
}
public class Key(
public int password;
)
现在是这样的,有一个需要用到钥匙的游戏,在每一关,你都可以得到一个钥匙包和一把钥匙。
现在来到了第一到关卡(第一个Activity),得到一条钥匙(Key
实例),然后开了门,然后就很自然的将钥匙放到了钥匙包(KeyCase
实例)里,等你准备过去第二关(第二个activity)的时候,你发现钥匙包压根带不过去(在android里,普通类是没办法从一个Activity带到另外一个activity的,只有可以序列化的类才可以),那没办法了,只能把钥匙包放下,先过去第二关看看了。等来到第二关(跳转到第二个Activity)的时候才发现,妈呀,上一关的钥匙居然还有用,可你现在会发现,如果你现在再获取到钥匙包这个对象的时候,里面却是空空如也,因为现在的钥匙包已经不是第一关的钥匙包了。如果这里的钥匙包以单例模式创建那就不一样了,因为在整个游戏下来,都只有一个钥匙包。这才更符合设定,毕竟谁闲来无事带几十个钥匙包出门(无奈程度基本跟用六位数密码去保管两位数的钱...无异)。总的来说,单例模式就是在一个系统中某些需要统一的地方,最好的方法就是让他唯一再通俗点,就是当你希望“这个类只有一个就好了”的时候就是使用单例模式的时候。
这样看来在某些方面,某些时候要是能保证单例也是不错的。
单例模式怎么搞?
首先控制实例的个数就是单例最核心的部分了,那么就不能让外界去创建了,因为那是不可控的,所以第一点就是这个类不能被外界new
。如何做到不被外界new
呢?将构造方法私有化就好了:
public class KeyCase{
private KeyCase(){}
//code ....
}
好了,现在外界谁都不能创建钥匙包KeyCase
了,问题又来了,那外界该怎么样获得这个钥匙包实例啊?
别人不能,但是自己(钥匙包)可以创建啊。
public class KeyCase{
private KeyCase keyCase = new KeyCase();
private KeyCase(){}
public KeyCase getInstance(){
return keyCase;
}
//code ....
}
但是外界还是拿不到,只有钥匙包的实例才能通过getInstance()
方法拿到keyCase
这个实例,可是目前也就只有这么一个实例,这样就陷入了一个死循环:要拿到KeyCase
的实例首先要有它的实例。很尴尬!!但是别怕,相信大家都见过这两个关键字了,static
和final
,一个static
就解决了上面的问题,而final
保证了不可变,所以代码最终就变成了这样:
public class KeyCase{
private static final KeyCase keyCase = new KeyCase();//准备好一个钥匙包,永远不变的钥匙包
private KeyCase(){}
public static KeyCase getInstance(){
return case;//我现在想用了就可以直接拿到,真方便
}
//code ....
}
这就是单例模式里面的“饿汉式”,饿汉式将钥匙包的对象keyCase
当作食物,饿汉首先准备好食物,等饿的时候(需要的时候)就可以直接拿来吃(用)。
懒汉式
懒汉式是单例的另一种“套路”,而懒汉式的意思就是我很懒,我不想像饿汉那样先准备好,我想等到我饿的时候才去准备。哟呵!够懒的。这样我们就饿汉的代码直接根据意思改:
//饿汉式
public class KeyCase{
private static KeyCase keyCase ;//我先不准备
private KeyCase(){}
public static KeyCase getInstance(){
if(keyCase == null){//我现在饿了,但是我想要的还没有
keyCase = new KeyCase();//那好吧,只能先去准备了
}
return keyCase;//准备好了,开吃! 3
}
//code ....
}
好了,饿汉和懒汉两种方式都实现了。但是,有缺陷哦!
目前的饿汉和懒汉的缺陷。
先说饿汉吧。
只能说是个小缺陷,准确点讲,也不算是缺陷,可以算是饿汉式的特点,那就是先准备,如果准备了不用,那就浪费了一点空间,饿汉式就是用空间来换取时间,先创建而消耗了部分空间去换取使用时候的时间,所以饿汉式在类加载的时候会比懒汉式要慢,但是在运行的时候会比懒汉式要快。而且饿汉还是线程安全的。没意思(饿汉式没什么缺陷可说当然没意思),还是来看看懒汉式吧。
线程不安全的懒汉式
上面说到饿汉式是空间换时间,而懒汉式就是时间换空间,这样就有一个问题,那就是线程不安全。如果现在有两条线程执行钥匙包的getInstance
方法,就有可能得到两个对象,因为不能保证在同一时间只有一条线程执行到case=new KeyCase();
这样代码。
小E:“我知道我知道,加个同步就好了”!(???这位小E同志是哪冒出来的?)
public class KeyCase{
private static KeyCase keyCase ;
private KeyCase(){}
public static KeyCase getInstance(){
if(keyCase == null){//1
synchorinzed(KeyCase.class){
keyCase = new KeyCase();//2
}
}
return keyCase;
}
//code ....
}
我:“不够”!
照着上面的例子,照样还是会有两个线程停留在1处,也就是都已经判断过了,就算排着队去执行2处的代码,还是会有两个实例。
小E:“现在我是真的知道了,再加个判断”:
public class KeyCase{
private static KeyCase keyCase ;
private KeyCase(){}
public static KeyCase getInstance(){
if(keyCase == null){//1
synchorinzed(KeyCase.class){
if(keyCase==null){//3
keyCase = new KeyCase();//2
}
}
}
return keyCase;
}
//code ....
}
我:“很接近了,但还是不够!”。是的,我也很长一段时间认为上面的是最安全的懒汉式了,然而看了一些大佬的文章我才知道,这样还是不够,缺陷是第一条线程已经执行完了2处的代码,生成了钥匙包的实例,当第二条线程来到3处是依旧有可能得到的信息是keyCase==null
,也就是第二条线程不能及时的知道,keyCase
已经被第一条线程创建好了。说到这里就说到java的内存模型了。但是这个在这就不细说了,大家知道有这么一种可能就是了。有兴趣的可以看一些有关volatile
关键字的文章。这就是解决懒汉式最后一道线程缺陷的关键字,volatile
保证了可见性,什么是可见性呢,也就是volatile
关键字修饰的变量对于线程们(多个线程)来说,无论是谁修改了这个变量,其他线程对这个行为是可见的,就立马知道。原来A线程这个小子偷偷修改了这个变量。还以为我不知道?我可是偷偷看着呢,不行,先记在本子上!!
线程最安全的懒汉式
所以最后线程安全的懒汉式应该是这样的:
public class KeyCase{
private static volatile KeyCase keyCase ;
private KeyCase(){}
public static KeyCase getInstance(){
if(keyCase == null){//1
synchorinzed(KeyCase.class){
if(keyCase==null){//3
keyCase = new KeyCase();//2
}
}
}
return keyCase;
}
//code ....
}
好了,单例模式最经典的两种已经说完了,估计对单例模式还不了解的应该印象深一点了吧,有加深一点点,对于我来说就已经足够了