设计模式之单例模式

从这篇开始我的设计模式学习之旅,以前面对设计模式总觉得是在看天书。果然,生活就是最好的老师,当在社会上摸爬滚打过一番之后,别管多久,都会多少有点收获,这恐怕就是生活的意义吧。闲话不多说,马上入正题:在入正题之前再啰嗦一句,由于本人最熟悉的是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这个类来说就不是单一实例了。
所以单例模式就是:有没有一种套路可以让我一个类只创建出来一个实例?
可是

我们为什么要单例模式呢?

想象一下,现实中有没有什么是只有一个的,当然哦,不是说全世界只有一个,而是在一个“系统”中只有一个,比如说在一部电脑里面电脑系统只有一个任务管理器,再比如说,在金庸先生的武侠世界里只有一个武林盟主。等等等等。可以看的出来,“单一”的一个好处就是:方便管理。我再举一个代码的例子吧:
现在有一个放钥匙的钥匙包:

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的实例首先要有它的实例。很尴尬!!但是别怕,相信大家都见过这两个关键字了,staticfinal,一个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 ....
}

看到了吧,只要一懒,就出来一堆毛病,所以,小E啊,你…,小E呢?哪去了?来无影去无踪啊??(左右找寻)!!
好了,单例模式最经典的两种已经说完了,估计对单例模式还不了解的应该印象深一点了吧,有加深一点点,对于我来说就已经足够了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值