设计模式之单例模式

什么是单例设计模式?

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。

具体实现

需要:

(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。

(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。

(3)定义一个静态方法返回这个唯一对象。

实现一:立即加载 / “饿汉模式”

立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”),常见的实现办法就是直接new实例化。

package com.Geeksun.singleton;

/**
 * “饿汉模式”的优缺点:
 *
 * 优点:实现起来简单,没有多线程同步问题。
 *
 * 缺点:当类Singleton01被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static
 * 的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,
 * 因此在某些特定条件下会耗费内存。
 */
public class Singleton01 {
    private static Singleton01 instance = new Singleton01();

    private Singleton01(){}

    public static Singleton01 getInstance(){
        return instance;
    }
}

实现二:延迟加载 / “懒汉模式”

延迟加载就是调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”),常见的实现方法就是在get方法中进行new实例化。

package com.Geeksun.singleton;

/**
 * “懒汉模式”的优缺点:
 *
 * 优点:1.在多线程情形下,保证了“懒汉模式”的线程安全。
 *       2.实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法
 *       第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
 *
 * 缺点:众所周知在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。
 */
public class Singleton02 {
    private static Singleton02 instance;

    private Singleton02(){}

    public static synchronized Singleton02 getInstance() {
        if(instance == null){
            instance = new Singleton02();
        }
        return instance;
    }
}

实现三:DCL双检查锁机制(DCL:double checked locking)

package com.Geeksun.singleton;

/**
 * “双重检查锁”的优缺点:
 *
 * 双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。
 *
 * 双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
 */
public class Singleton03 {
    private volatile static Singleton03 instance;//使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

    private Singleton03(){}

    public static Singleton03 getInstance() {
        if(instance == null){
            synchronized (Singleton03.class){
                if(instance == null){
                    instance = new Singleton03();
                }
            }
        }
        return instance;
    }
}

实现四:静态内部类

package com.Geeksun.singleton;

/**
 * 静态内部类
 *注解:定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在
 * Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。
 * 优点:达到了lazy loading的效果,即按需创建实例。
 * 
 */
public class Singleton04 {

    private Singleton04(){
    }

    private static class SingletonHolder{
        private static final Singleton04 instance = new Singleton04();
    }

    public Singleton04 getInstance(){
        return SingletonHolder.instance;
    }
}

实现五:枚举类

package com.Geeksun.singleton;

/**
 * “枚举类”
 *  这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,
 *   可谓是很坚强的壁垒啊,书写较为方便。
 *
 */
public enum  Singleton05 {
    INSTANCE;

    public void operation(){

    }
}

 

除了枚举类外,单例模式可以被破解

package com.Geeksun.singleton;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

public class Client {

    public static void main(String[] args) throws Exception {
        //通过反射破解单例模式
        Singleton01 sc1 = Singleton01.getInstance();
        Singleton01 sc2 = Singleton01.getInstance();
        Class<Singleton01> clazz = (Class<Singleton01>)Class.forName("com.Geeksun.singleton.Singleton01");
        Constructor<Singleton01> c = clazz.getDeclaredConstructor();
        c.setAccessible(true);
        Singleton01 sc3 = c.newInstance();
        Singleton01 sc4 = c.newInstance();
        System.out.println(sc1);
        System.out.println(sc2);
        System.out.println(sc3);
        System.out.println(sc4);
        
        //通过反序列化破解单例模式
        FileOutputStream fis = new FileOutputStream("D:/test.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fis);
        oos.writeObject(sc1);
        oos.close();
        fis.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/test.txt"));
        Singleton01 sc5 = (Singleton01)ois.readObject();
        System.out.println(sc1);
        System.out.println(sc5);
    }
}

对于饿汉模式,防止破解的方法是

public class Singleton01 implements Serializable {
    private static Singleton01 instance = new Singleton01();

    private Singleton01(){
        //如果已经创建了对象,则不能调用无参构造函数!
        if(instance != null){
            throw new RuntimeException();
        }
    }

    public static Singleton01 getInstance(){
        return instance;
    }

    //反序列化时,如果定义了readResolve()则直接返回该方法指定的对象,不需要指定新的对象。
    private Object readResolve(){
        return instance;
    }
}

 

;