Java设计模式之一:单例模式

单例模式是设计模式中使用最为普遍的模式之一。它是一只对象创建模式,用于产生一个对象的具体实现,它可以确保系统中一个类只产生一个实例。在Java语言中,这样的行为能够带来两大好处:
1)对于频繁使用的对象,可以省略创建对象所花费的时间。这对于那些重量级对象而言,是非常可观的一笔系统开销;
2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
因此对于系统的关键组件和被频繁使用的对象,使用单例模式便可以有效地改善系统的性能。
单例模式的参与者非常简单,只有单例类(提供单例的工厂,返回单例)和使用者(获取并使用单例)两个:
Java设计模式之一:单例模式

1)SimpleSingleton

package com.panny;
public class SimpleSingleton {
                            
    /*
     * 这种单例的实现方式非常简单,而且十分可靠。
     * 它唯一的不足是无法对instance实例做延迟加载。假如单例的创建过程很慢,而由于instance成员变量
     * 是static定义的,因此在JVM加载单例时,单例对象就会被立即建立,如果此时,这个单例类在系统中还扮演着
     * 其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,而不管是否会被用到。
     * */
                            
    private static SimpleSingleton inistance = new SimpleSingleton();
    private SimpleSingleton() {
        System.out.println("Creating SimpleSingleton..."); // 创建单例的过程可能会比较慢
    }
                            
    public static SimpleSingleton getInstence() {
        return inistance;
    }
                            
    public static void greeting() {
        System.out.println("Hello, SimpleSingleton...");
    }
}


2)  lazySingleton

package com.panny;
public class LazySingleton {
                         
    private static LazySingleton instance = null;
                         
    private LazySingleton() {
        System.out.println("Creating LazySingleton...");
    }
                         
    /*再次需要特别注意:getInstance()方法必须是同步的。
    否则在多线程环境下,当线程1正新建单例时,完成赋值操作前,
    线程2可能判断instance为null,故线程2也将新建单例,
    而导致多个实例被创建, 故同步关键字是必须的。*/
    public static synchronized LazySingleton getInstance() {
        if(null == instance) {
            instance = new LazySingleton();
        }
                             
        return instance;
    }
                         
    public static void greeting() {
        System.out.println("Hello, LazySingleton...");
    }
}


3)  StaticSingleton

package com.panny;
public class StaticSingleton {
                      
    /*在这个实现中,单例模式使用内部类来维护单例的实例,
     * 当StaticSingleton被加载时,其内部类不会被初始化,
     * 故可以确保当StaticSingleton被载入JVM时,不会初始化单例类,
     * 而当getInstence()方法被调用时,才会加载内部类(StaticSingletonHolder),
     * 从而初始化instance。同时,由于实例的建立是在类加载时完成,故天生对多线程友好,
     * getInstence()方法也不需要使用synchronized关键字。*/
                      
    private StaticSingleton() {
        System.out.println("Creating StaticSingleton...");
    }
                      
    private static class StaticSingletonHolder {
        private static StaticSingleton instance = new StaticSingleton();
    }
                      
    public static StaticSingleton getInstence() {
        return StaticSingletonHolder.instance;
    }
}


4)  SerSingleton

package com.panny;
public class SerSingleton implements java.io.Serializable {
                   
    private static SerSingleton instance = new SerSingleton();
                   
    private SerSingleton() {
        System.out.println("Creating SerSingleton...");
    }
                   
    public static SerSingleton getInstance() {
        return instance;
    }
                   
    private Object readResolve() {
        return instance;
    }
}


5) MyTest

package com.panny;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
                    
                    
//      SimpleSingleton.greeting();
        /*
         * Creating SimpleSingleton...
         * Hello, SimpleSingleton...
         *
         * 可以看到,虽然此时并没有使用单例类,但它还是被创建出来,这也许是开发人员不愿意见到的。
         * 为了解决这个问题,并以此提高系统在相关函数调用时的反应速度,就需要引入延迟加载机制:LazySingleton
         */
                    
//      LazySingleton.greeting(); // Hello, LazySingleton...
                    
        /*
         * 虽然LazySingleton实现了延迟加载的功能,但和SimpleSingleton相比,
         * 它引入了synchronized关键字,因此在多线程环境中,它的耗时要远远大于SimpleSingleton,
         * 以下测试码说明了这个问题:
         * */
                    
        class MyThread implements Runnable {
            String name;
            public MyThread(String name) {
                this.name = name;
            }
                        
            @Override
            public void run() {
                long beginTime = System.currentTimeMillis();
                            
                for(int i = 0; i < 1000000; i++) {
//                  SimpleSingleton.getInstence();
                    /*T1 spend: 7
                    T3 spend: 12
                    T4 spend: 12
                    T5 spend: 11
                    T2 spend: 12*/
                                
                    LazySingleton.getInstance();
                    /*T1 spend: 259
                    T3 spend: 266
                    T5 spend: 269
                    T4 spend: 272
                    T2 spend: 273*/
                                
//                  StaticSingleton.getInstence();
                    /*T5 spend: 6
                    T1 spend: 12
                    T4 spend: 11
                    T2 spend: 7
                    T3 spend: 11*/
                }
                            
                System.out.println(this.name + " spend: " + (System.currentTimeMillis() - beginTime));
                            
            }
                        
        }
                    
        Thread t1 = new Thread(new MyThread("T1"));
        t1.start();
        Thread t2 = new Thread(new MyThread("T2"));
        t2.start();
        Thread t3 = new Thread(new MyThread("T3"));
        t3.start();
        Thread t4 = new Thread(new MyThread("T4"));
        t4.start();
        Thread t5 = new Thread(new MyThread("T5"));
        t5.start();
                    
        /*开启5个线程同时运行
         * 两种类型性能至少相差2个数量级,
         * 为了使用延迟加载引入的synchronized关键字反而降低了系统性能,是不是有些得不偿失呢?
         * 为了解决这个问题,需要对其进行改进:StaticSingleton
         * 查看测试码,实现了性能改进。*/
                    
        /*通常情况下,用以上方式实现的单例已经可以确保在系统中只存在唯一实例了。
         * 但仍然有例外的情况可能导致系统生成多个实例,比如:1)在代码中通过反射机制,强行调用
         * 单例类的私有构造函数,生成多个单例;2)通过java.io.Serializable序列化单例类生成多个单例。
         * 第一种方式太极端,一般不会遇见,以下尝试解决第二种方式产生的问题:SerSingleton */
                    
        SerSingleton s0 = SerSingleton.getInstance();
                    
        FileOutputStream fos = new FileOutputStream("SerSingleton.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
                    
        oos.writeObject(s0);
        oos.flush();
        oos.close();
                    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SerSingleton.txt"));
                    
        SerSingleton s1 = (SerSingleton) ois.readObject();
        ois.close();
                    
        System.out.println(s0 == s1);
        /*当去掉SerSingleton中的readResolve()方法时,s0 == s1为false,
         * 说明s0和s1分别引用了不同的实例,即在反序列化后生成了多个实例对象。
         * 而当恢复SerSingleton中的readResolve()方法时,s0 == s1为true,
         * 说明即便经过了反序列化,仍然保持了单例的特征。
         * 实际上,在实现了私有的readResolve()方法后,readObject()已经形同虚设,
         * 它直接使用readResolve()替换了原本的返回值,从而形式上构造了单例。
         * 注意:序列化和反序列化可能会破坏单例,一般来说,对单例进行序列化和反序列化
         * 的场景不多,但如果存在,就要多加注意。*/
                    
    }
}


鸣谢:感谢葛一鸣老师的《Java程序性能与优化》,从中我学习了许多珍贵的编程技巧与经验。

本文出自 “好寂寞先生” 博客,请务必保留此出处http://pannyhjm.blog.51cto.com/4473736/1384382

Java设计模式之一:单例模式,布布扣,bubuko.com

Java设计模式之一:单例模式

上一篇:windows到linux下面文本从ansi转utf-8(java调用shell命令,java文件复制)


下一篇:java笔记之java内存结构