`

研磨设计模式之单例模式-4

 
阅读更多

3.8  在Java中一种更好的单例实现方式

        根据上面的分析,常见的两种单例实现方式都存在小小的缺陷,那么有没有一种方案,既能够实现延迟加载,又能够实现线程安全呢?
        还真有高人想到这样的解决方案了,这个解决方案被称为Lazy initialization holder class模式,这个模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全。
1:先来看点相应的基础知识
        先简单的看看类级内部类相关的知识。

  • 什么是类级内部类?
        简单点说,类级内部类指的是:有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
  • 类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
  • 类级内部类中,可以定义静态的方法,在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
  • 类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载

        再来看看多线程缺省同步锁的知识。
        大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:

  • 由静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据时 
  • 访问 final 字段时 
  • 在创建线程之前创建对象时 
  • 线程可以看见它将要处理的对象时

2:接下来看看这种解决方案的思路
        要想很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程安全性。比如前面的“饿汉式”实现方式,但是这样一来,不是会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。
        如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例,这样一来,只要不使用到这个类级内部类,那就不会创建对象实例。从而同时实现延迟加载和线程安全。
        看看代码示例可能会更清晰,示例代码如下:

Java代码  收藏代码
  1. public class Singleton {  
  2.     /** 
  3.      * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 
  4.      * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 
  5.      */  
  6.     private static class SingletonHolder{  
  7.         /** 
  8.          * 静态初始化器,由JVM来保证线程安全 
  9.          */  
  10.         private static Singleton instance = new Singleton();  
  11.     }  
  12.     /** 
  13.      * 私有化构造方法 
  14.      */  
  15.     private Singleton(){  
  16.     }  
  17.     public static  Singleton getInstance(){  
  18.         return SingletonHolder.instance;  
  19.     }  
  20. }  

        仔细想想,是不是很巧妙呢!
        当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会被虚拟机在装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
        这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。


3.9  单例和枚举

        按照《高效Java 第二版》中的说法:单元素的枚举类型已经成为实现Singleton的最佳方法。
        为了理解这个观点,先来了解一点相关的枚举知识,这里只是强化和总结一下枚举的一些重要观点,更多基本的枚举的使用,请参看Java编程入门资料:

  • Java的枚举类型实质上是功能齐全的类,因此可以有自己的属性和方法
  • Java枚举类型的基本思想:通过公有的静态final域为每个枚举常量导出实例的类
  • 从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举

        用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可,示例代码如下:

Java代码  收藏代码
  1. /** 
  2.  * 使用枚举来实现单例模式的示例 
  3.  */  
  4. public enum Singleton {   
  5.     /** 
  6.      * 定义一个枚举的元素,它就代表了Singleton的一个实例 
  7.      */  
  8.     uniqueInstance;  
  9.       
  10.     /** 
  11.      * 示意方法,单例可以有自己的操作 
  12.      */  
  13.     public void singletonOperation(){  
  14.         //功能处理  
  15.     }  
  16. }  

 

        使用枚举来实现单实例控制,会更加简洁,而且无偿的提供了序列化的机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。


3.10  思考单例模式

1:单例模式的本质
        单例模式的本质:控制实例数目
        单例模式是为了控制在运行期间,某些类的实例数目只能有一个。可能有人就会想了,那么我能不能控制实例数目为2个,3个,或者是任意多个呢?目的都是一样的,节省资源啊,有些时候单个实例不能满足实际的需要,会忙不过来,根据测算,3个实例刚刚好,也就是说,现在要控制实例数目为3个,怎么办呢?
        其实思路很简单,就是利用上面通过Map来缓存实现单例的示例,进行变形,一个Map可以缓存任意多个实例,新的问题就是,Map中有多个实例,但是客户端调用的时候,到底返回那一个实例呢,也就是实例的调度问题,我们只是想要来展示设计模式,对于这个调度算法就不去深究了,做个最简单的,循环返回就好了,示例代码如下:

Java代码  收藏代码
  1. /** 
  2. * 简单演示如何扩展单例模式,控制实例数目为3个  
  3. */  
  4. public class OneExtend {  
  5.     /** 
  6.     * 定义一个缺省的key值的前缀 
  7.     */  
  8.     private final static String DEFAULT_PREKEY = "Cache";  
  9.     /** 
  10.     * 缓存实例的容器 
  11.     */  
  12.     private static Map<String,OneExtend> map =   
  13. new HashMap<String,OneExtend>();  
  14.     /** 
  15.     * 用来记录当前正在使用第几个实例,到了控制的最大数目,就返回从1开始 
  16.     */  
  17.     private static int num = 1;  
  18.     /** 
  19.     * 定义控制实例的最大数目 
  20.     */  
  21.     private final static int NUM_MAX = 3;   
  22.     private OneExtend(){}  
  23.     public static OneExtend getInstance(){  
  24.         String key = DEFAULT_PREKEY+num;  
  25.         //缓存的体现,通过控制缓存的数据多少来控制实例数目  
  26.         OneExtend oneExtend = map.get(key);  
  27.         if(oneExtend==null){  
  28.             oneExtend = new OneExtend();  
  29.             map.put(key, oneExtend);  
  30.         }  
  31.         //把当前实例的序号加1  
  32.         num++;  
  33.         if(num > NUM_MAX){  
  34.             //如果实例的序号已经达到最大数目了,那就重复从1开始获取  
  35.             num = 1;  
  36.         }  
  37.         return oneExtend;         
  38.     }  
  39.       
  40.     public static void main(String[] args) {  
  41.         //测试是否能满足功能要求  
  42.         OneExtend t1 = getInstance ();  
  43.         OneExtend t2 = getInstance ();  
  44.         OneExtend t3 = getInstance ();  
  45.         OneExtend t4 = getInstance ();  
  46.         OneExtend t5 = getInstance ();  
  47.         OneExtend t6 = getInstance ();  
  48.           
  49.         System.out.println("t1=="+t1);  
  50.         System.out.println("t2=="+t2);  
  51.         System.out.println("t3=="+t3);  
  52.         System.out.println("t4=="+t4);  
  53.         System.out.println("t5=="+t5);  
  54.         System.out.println("t6=="+t6);  
  55.     }  
  56. }  

 测试一下,看看结果,如下:

Java代码  收藏代码
  1. t1==cn.javass.dp.singleton.example9.OneExtend@6b97fd  
  2. t2==cn.javass.dp.singleton.example9.OneExtend@1c78e57  
  3. t3==cn.javass.dp.singleton.example9.OneExtend@5224ee  
  4. t4==cn.javass.dp.singleton.example9.OneExtend@6b97fd  
  5. t5==cn.javass.dp.singleton.example9.OneExtend@1c78e57  
  6. t6==cn.javass.dp.singleton.example9.OneExtend@5224ee  

 

        第一个实例和第四个相同,第二个与第五个相同,第三个与第六个相同,也就是说一共只有三个实例,而且调度算法是从第一个依次取到第三个,然后回来继续从第一个开始取到第三个。
        当然这里我们不去考虑复杂的调度情况,也不去考虑何时应该创建新实例的问题。
        注意:这种实现方式同样是线程不安全的,需要处理,这里就不再展开去讲了。


2:何时选用单例模式
        建议在如下情况中,选用单例模式:

  • 当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题

 

3.11  相关模式


        很多模式都可以使用单例模式,只要这些模式中的某个类,需要控制实例为一个的时候,就可以很自然的使用上单例模式。比如抽象工厂方法中的具体工厂类就通常是一个单例。

 

 

 

                                                            单例模式结束

 

注:本文转自   http://chjavach.iteye.com

分享到:
评论

相关推荐

    研磨设计模式之单例模式

    通过研磨设计模式之单例模式的资料,你可以深入理解单例模式的原理、实现方式及其优缺点,进一步提升自己的编程技能和设计思维。学习并熟练掌握设计模式,对于成为一名优秀的Java开发者至关重要。

    研磨设计模式源码

    《研磨设计模式源码》是一份非常宝贵的资源,它提供了设计模式的实践代码,帮助开发者深入理解并应用这些模式。设计模式是软件工程中经过长期实践总结出来的一套通用解决方案,它们描述了在特定场景下如何解决常见...

    研磨设计模式(完整带书签).part2.pdf

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式-配套源代码 UTF-8格式

    《研磨设计模式》是一本深入探讨软件设计原则与实践的经典书籍,其配套源代码提供了丰富的实例,帮助读者更好地理解和应用各种设计模式。这个UTF-8格式的压缩包包含了书中介绍的各种设计模式的实现,是学习和研究...

    研磨设计模式--chjavach的博客文章

    研磨设计模式的过程是持续学习和实践的过程,chjavach的博客文章提供了深入探讨这些模式的宝贵资源,值得我们仔细阅读和学习。通过深入理解和运用这些设计模式,可以提升个人的编程技巧,同时也为团队合作和项目维护...

    研磨设计模式PDF

    《研磨设计模式》这本书是陈臣和王斌两位作者合作的成果,专注于讲解软件设计中的模式应用。设计模式是软件工程中的一种最佳实践,它总结了在特定上下文中解决问题的常见方法,使得开发者可以复用这些解决方案,提高...

    研磨设计模式-配套源代码

    以上只是设计模式中的一部分,研磨设计模式的配套源代码可能涵盖了这些或更多的模式。通过学习这些源代码,你可以更深入地理解每个模式的实现细节,以及如何在实际项目中灵活应用。这将有助于提升你的编程技能,使...

    研磨设计模式-配套源代码.7z

    《研磨设计模式》是一本深入探讨软件设计模式的书籍,其配套源代码包含了许多经典设计模式的实际应用示例。这些源代码可以帮助读者更直观地理解设计模式的原理和使用方法,进一步提升软件开发能力。 设计模式是软件...

    研磨设计模式[书签]

    《研磨设计模式》是由陈臣和王斌合著,由清华大学出版社出版的一本深入探讨设计模式的专业书籍。设计模式是软件工程中的一个重要概念,它代表了在特定上下文中解决问题的常见方法,经过时间和实践的验证,具有很高的...

    研磨设计模式-配套源代码.rar

    《研磨设计模式》是一本深入探讨软件设计模式的书籍,配套源代码是作者为了帮助读者更好地理解和应用书中介绍的设计模式而提供的实践示例。设计模式是软件开发中经过实践检验的、解决常见问题的模板,它为软件设计...

    研磨设计模式博文集

    这个“研磨设计模式博文集”显然是一份深入探讨设计模式的资料集合,其中可能包含了对多种设计模式的详细解析、示例代码以及实际应用中的经验分享。在软件开发中,设计模式能够帮助开发者提高代码质量、可读性和可...

    研磨设计模式全部源代码

    这个压缩包“研磨设计模式全部源代码”包含了多种设计模式的实现,这些模式可以帮助开发者写出更可维护、可扩展和可复用的代码。下面将详细讲解其中可能包含的一些重要设计模式及其应用。 1. 工厂模式:这是最简单...

    研磨设计模式.rar

    《研磨设计模式》这本书是软件开发领域中的经典之作,主要关注的是面向对象设计中的设计模式。设计模式是在特定上下文中解决常见问题的最佳实践,它为开发者提供了在类似情况下重复使用解决方案的模板,有助于提高...

    研磨设计模式讲课PPT

    研磨设计模式是一本深入探讨软件设计原则与实践的书籍,其讲课PPT为我们提供了丰富的设计模式知识。设计模式是软件工程中经过实践验证的、解决常见问题的模板,是经验丰富的开发人员智慧的结晶。这些模式可以帮助...

    研磨设计模式(完整带书签).part1.pdf

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式 源代码

    《研磨设计模式》是一本深入探讨软件设计模式的经典书籍,源代码包含了书中所讲解的各种设计模式的实际应用示例。设计模式是软件工程中的重要概念,它们是经过反复验证、在特定情境下解决常见问题的有效解决方案。...

    研磨设计模式-part4

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式 演示源代码

    "研磨设计模式 演示源代码"这个资源包含了对设计模式的详细解释和实例分析,旨在帮助学习者深入理解和应用这些模式。 1. **单例模式**:确保一个类只有一个实例,并提供一个全局访问点。在资源管理、缓存或者线程池...

Global site tag (gtag.js) - Google Analytics