【秒懂设计模式】单例设计模式

来源:互联网 发布:c语言 函数调用 编辑:程序博客网 时间:2024/06/05 18:55

 秒懂设计模式——单例设计模式


(三)单例设计模式

1.先解释一下,什么是单例模式呢?

Java中是这样定义的:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。

显然从单例模式的定义中,我们可以发现它有三个要点

①某个类只能有一个实例;

②它必须自行创建这个实例;

③它必须自行向整个系统提供这个实例。

2.要满足这三个要点,应该如何实现呢?下面让我们来逐条分析:

①如何保证某个类只能有一个实例?

让我先来想一下,一个类的对象是如何创建的呢?答案是:一个类的对象的产生是由类构造函数来完成。那么,我们是不是可以通过私有类构造函数来实现呢?

②如何保证类定义中含有一个该类的静态私有对象?

这个就很简单了,让单例类自己创建自己的唯一实例

③如何保证它自行向整个系统提供这个实例?

这个也不难,对外提供获取此对象的静态方法即可。

3.单例模式分为几种呢?

①懒汉式,线程不安全;

②懒汉式,线程安全;

③饿汉式;

④双检锁/双重校验锁(DCL,即 double-checked locking);

⑤登记式/静态内部类;

⑥枚举式;

下面仍然通过一个故事,逐一介绍这几种类型的单例模式,并且在最后,还会分析一下他们的性能差异。

【讲故事】某野鸡大学(),只有一个学生会主席(唯一对象,自行实例化),还是一个漂亮妹子叫“M蓉”,其他附近野鸭大学的学长都喜欢请她去自己宿舍,连夜补习英语(向整个系统提供实例)。

1)懒汉式,线程不安全

先解释一下为什么叫懒汉式:“懒”顾名思义就是延迟,懒汉式就是指,只有在获取此单例对象时,才会去创建此对象

Java代码】

①创建一个单例模式的类。

package com.liyan.lazy;/** * 懒汉式的M蓉(线程不安全) * <p>Title: LazyMRong</p>   * @author  Liyan   * @date    2017年4月27日 下午2:00:44 */public class LazyMRong {//1.私有空参构造,防止别人创建private LazyMRong(){}//2.自己创建自己的唯一实例private static LazyMRong lazyMRong ;//3.对外提供获取此对象的静态方法public static LazyMRong getLazyMRong() {if (lazyMRong == null) {//懒汉式意味着,只有你在想获取时,才创建此对象return new LazyMRong();}return lazyMRong;}public void teachEnglish() {System.out.println("连夜补习外语!");}}

②创建外部访问

package com.liyan.lazy;/** * S喆请M蓉补习外语 * <p>Title: SZhe</p>   * @author  Liyan   * @date    2017年4月27日 下午2:15:23 */public class SZhe {public static void main(String[] args) {//通过静态方法,获取到单例对象LazyMRong lazyMRong = LazyMRong.getLazyMRong();//调用补习外语方法lazyMRong.teachEnglish();}}

分析:首先懒汉式肯定是延迟初始化的,但是不支持多线程。因为没有加锁synchronized,所以有些资料上认为,从严格意义上讲,它并不算单例模式

2)懒汉式,线程安全

这个跟上面的懒汉式,线程不安全,唯一的区别就是:在获取单例类的静态方法上,加上了synchronized关键字进行修饰

package com.liyan.lazy;/** * 懒汉式的M蓉(线程安全) * <p>Title: LazyMRong</p>   * @author  Liyan   * @date    2017年4月27日 下午2:00:44 */public class LazyMRong {//1.私有空参构造,防止别人创建private LazyMRong(){}//2.声明自己的唯一实例,先不实例化private static LazyMRong lazyMRong ;//3.对外提供获取此对象的静态方法public synchronized static LazyMRong getLazyMRong() {if (lazyMRong == null) {//懒汉式意味着,只有你在想获取时,才创建此对象return new LazyMRong();}return lazyMRong;}public void teachEnglish() {System.out.println("连夜补习外语!");}}

3)饿汉式分析:毋庸置疑,它是延迟初始化的,能够在多线程中很好的工作,同时在第一次调用时,才进行初始化,避免了内存的浪费。但是加了synchronized 锁机制,所以效率很低

先解释一下为什么叫饿汉式:“饿”意味着“着急”,饿汉式就是指,不管单例对象有没有外部访问,先实例化再说

Java代码】饿汉式的M蓉

package com.liyan.hungry;/** * 饿汉式的M蓉(线程安全) * <p>Title: HungryMRong</p>   * @author  Liyan   * @date    2017年4月27日 下午3:02:18 */public class HungryMRong {//1.私有空参构造,防止别人创建private HungryMRong(){}//2.创建自己的唯一对象,并直接实例化(饿汉式,因为饿着急,不管三七二十一,直接实例化)private static HungryMRong hungryMRong = new HungryMRong();//3.对外提供获取此对象的静态方法public static HungryMRong getHungryMRong() {return hungryMRong;}}

4)双重检查锁(DCL,即 double-checked locking)分析:显然饿汉式,不懒,所以没有延迟初始化,同时它基于 classloder 机制,而没用加锁的方式,所以既避免了多线程的同步问题,又使得执行效率得到提高。但是,因为它在类加载时就初始化,所以会造成内存的浪费

先解释一下为什么叫双重检查锁:顾名思义,会涉及到两把锁:

①进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块;

②进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建该实例。

Java代码】双重检查锁的M蓉

package com.liyan.dcl;/** * DCL双重检查锁的单例模式的M蓉 * <p>Title: DCLMrong</p>   * @author  Liyan   * @date    2017年4月27日 下午4:54:30 */public class DCLMrong {//1.私有空参构造,防止别人创建private DCLMrong (){}//2.声明唯一的单例对象;注意这里用到了volatile关键字!private volatile static DCLMrong dclMrong ;//3.对外提供获取此对象的静态方法public static CLMrong getDclMrong() {//第一把锁:先检查实例是否存在,如果不存在才进入下面的同步块;if (dclMrong == null) {synchronized (DCLMrong.class) {//第二把锁:再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。if (dclMrong == null) {return new DCLMrong();}}}return dclMrong;}}

分析:这种方式采用双锁机制,安全且在多线程情况下能保持高性能说明: 双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量

5)登记式/静态内部类

先解释一下为什么叫登记式:见名知意,想要获取某单例对象,没登记的必须先登记,然后才能获取。此时我们还需要一个登记簿(Map集合),当然,这个登记簿可以记录一堆单例对象,而不仅仅是一个。

这个模式确实复杂了一些,而且很多网上的例子也是觉得有所不妥,我根据个人理解,写了如下的代码,如果各位看官觉得也有疑问,欢迎留言讨论。

Java代码】

①创建登记式单例模式类

package com.liyan.register;import java.util.HashMap;import java.util.Map;/** * 登记式单例模式 * <p>Title: RegisterMrong</p>   * @author  Liyan   * @date    2017年4月27日 下午6:38:22 */public class RegSingleton {//1.私有空参构造,防止别人创建private RegSingleton(){}//2.创建一个登记簿,在静态代码块中创建自己的唯一对象private static Map<String, RegSingleton> map = new HashMap<String, RegSingleton>(0);  //在登记簿上登记private static void singletonHolder(String name) {if(name==null){              name="RegSingleton";         } RegSingleton singleton = new RegSingleton();  //利用反射获取类名,并作为map集合的keymap.put(name, singleton);System.out.println("已将"+ name +"记录到登记簿!");}//3.对外提供获取此对象的静态方法public static RegSingleton getInstance(String name){          if(name==null){              name="RegSingleton";             System.out.println("名字为空,自动找RegSingleton!");        }          //查询登记簿上是否登记过        if(map.get(name)==null){          System.out.println("名字"+name+"未在登记簿中登记!");            try {              //如果没有登记过,则先进行登记            System.out.println("名字"+name+"开始在登记簿中登记!");            RegSingleton.singletonHolder(name);            //登记之后再返回该对象            System.out.println("名字"+name+"已经登记完成!");            return map.get(name);            } catch (Exception e) {                  e.printStackTrace();              }          }else {        //如果登记过,直接返回该单实例        System.out.println("名字"+name+"之前在登记簿中登记过了!");        return map.get(name);  }return null;    } public void teachEnglish() {System.out.println("连夜补习外语!");}}

②创建外部访问

package com.liyan.register;/** * S喆请M蓉补习外语 * <p>Title: SZhe</p>   * @author  Liyan   * @date    2017年4月27日 下午2:15:23 */public class SZhe {public static void main(String[] args) {RegSingleton mrong = RegSingleton.getInstance("Mrong");mrong.teachEnglish();}}

③结果

名字Mrong未在登记簿中登记!名字Mrong开始在登记簿中登记!已将Mrong记录到登记簿!名字Mrong已经登记完成!连夜补习外语!

①相同点:都利用ClassLoder机制,来保证初始化时只有一个线程。分析:主要是比较一下,登记模式和双重检验锁式有何异同?

②不同点:双重检验锁式在类一被装载是就被初始化了,所以它没有延迟的效果;而登记模式,只有再主动调用获取该对象的静态方法时,才被初始化,所以它有延迟效果。

6)枚举式

先解释一下为什么叫枚举式:不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,但是《Effective Java》一书中的话有这样一段很经典的话:“单元素的枚举类型已经成为实现Singleton的最佳方法!

Java代码】枚举单例模式的M蓉

package com.liyan.enummodel;/** * 枚举单例模式的M蓉 * <p>Title: EnumMrong</p>   * @author  Liyan   * @date    2017年4月27日 下午9:23:59 */public class EnumMrong {//1.私有空参构造,防止别人创建private EnumMrong() {}//2.申明自己的唯一对象public static EnumMrong getInstance() {return Singleton.INSTANCE.getInstance();}//3.对外提供获取此对象的静态方法private static enum Singleton {INSTANCE;private EnumMrong singleton;//在构造方法中实例化对象,保证只调用一次private Singleton() {singleton = new EnumMrong();}public EnumMrong getInstance() {return singleton;}}}



 
 

0 0