<span style="font-family: 'Microsoft YaHei'; font-size: 18px;">本文是设计模式学习笔记的第二篇文章,主要分析的是单例模式。包括懒汉式,饿汉式,登记式,以及懒汉式的改进型,还有一个关于读取propertoes配置文件的实例。这是第三节,本来想把这这节和上一节放在一起的,但是内容太多了,于是拆成了2次上一节分析了在数量上有所扩展的单例模式--登记式。这次我们来看一下在第一节中提到的,具有懒汉式和饿汉式俩种特点的解决方案。 这个解决方案的名字是Lazy initialization holder class。这个模式综合运用了java的类级内部类和多线程缺省同步锁的知识。 先来补充一下基础知识,以下内容来源自清华大学出版社的《研磨设计模式》。 先简单看看类级内部类相关的知识 *什么是类级内部类? 简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类叫对象级内部类。 *类级内部类相当于其外部类的static成分,他的对象与外部类对象间不存在依赖关系,因此可直接创建,而对象级内部类的实例,是绑定在外部对象实例中的。 *类级内部类中,可以定义静态的方法。在静态的方法中只能够引用外部类的中的静态成员方法或者成员变量。 *类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。 再来看看多线程缺省同步锁的知识。 大家都知道,在多线程开发中,为了解决兵法问题,主要通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含的执行了同步,这些情况下就不用自己再来进行同步控制了,这些情况包括: *由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时 *访问 final 字段时 *再创建线程之前创建对象时 *线程可以看见他将要处理的对象时 </span>
由此想要很简单的实现线程安全,可以采用静态初始化器的方式,他可以由JVM来保证线程的安全性。比如第一节的饿汉式实现方式。但是这样一来,会浪费一定的空间,因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。
如果现在有一种方法能够让类装载的时候不会初始化对象,不就解决问题了?一种客卿的方式就是采用类级内部类,在这个累计内部类里面去创建对象实例。这样一来,只要不适用这个类级内部类,那就不会创建对象实例。从而同时实现延迟加载和线程安全。
来看一下代码:
- /**
- * 懒汉式单例模式改进
- * 实现延迟加载,缓存
- * Lazy initialization holder class
- * 这个模式综合运用了java的类级内部类和多线程缺省同步锁的知识
- * @author qian.xu
- *
- */
- public class MySingleton2a {
- /**
- * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
- * 没有绑定的关系,而且只有被调用到才会装载,从而实现了延迟加载
- * @author qian.xu
- *
- */
- private static class Singleton{
- /**
- * 静态初始化器,用JVM来保证线程安全
- */
- private static MySingleton2a singleton = new MySingleton2a();
- static {
- System.out.println("---->类级的内部类被加载");
- }
- private Singleton(){
- System.out.println("---->类级的内部类构造函数被调用");
- }
- }
- //私有化构造函数
- private MySingleton2a(){
- System.out.println("-->开始调用构造函数");
- }
- //开放一个公有方法,判断是否已经存在实例,有返回,没有新建一个在返回
- public static MySingleton2a getInstance(){
- System.out.println("-->开始调用公有方法返回实例");
- MySingleton2a s1 = null;
- s1 = Singleton.singleton;
- System.out.println("-->返回单例");
- return s1;
- }
- }
然后是客户端代码:
- /**
- * 懒汉式单例模式改进
- * 实现了延迟加载
- * MySingleton2
- */
- public static void myprint2a(){
- System.out.println("---------------懒汉式单例模式改进--------------");
- System.out.println("第一次取得实例(改进懒汉式)");
- MySingleton2a s1 = MySingleton2a.getInstance();
- System.out.println("第二次取得实例(改进懒汉式)");
- MySingleton2a s2 = MySingleton2a.getInstance();
- if(s1==s2){
- System.out.println(">>>>>s1,s2为同一实例(改进懒汉式)<<<<<");
- }
- System.out.println();
- }
- /**
- * @param args
- */
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- //懒汉式
- //myprint();
- //饿汉式
- //myprint2();
- //懒汉式改进
- myprint2a();
- //登记式
- //myprint3();
- }
输出结果为:
---------------懒汉式单例模式改进--------------
第一次取得实例(改进懒汉式)
-->开始调用公有方法返回实例
-->开始调用构造函数
---->类级的内部类被加载
-->返回单例
第二次取得实例(改进懒汉式)
-->开始调用公有方法返回实例
-->返回单例
>>>>>s1,s2为同一实例(改进懒汉式)<<<<<
根据打印结果我们看出,内部类被加载的时间在外部类调用构造函数后。也就是说,第一次装载外部类的时候,内部类没有被加载,一直到我们调用s1 = Singleton.singleton时,内部类才被加载(延迟加载),又因为他是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证他的线程安全性。
这个解决方案的优势在于:getInstance方法并没有被同步,并且只是执行的一个域的访问,因此延迟初始化并没有增加任何访问成本~