设计模式之单例模式

173 阅读5分钟

确保一个类在任何情况下都绝对只有一个实例,并提供一个全 局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛,例如,公司 CEO、部门经 理 等 。 J2EE 标 准 中 的 ServletContext 、 ServletContextConfig 等 、 Spring 框 架 应 用 中 的 ApplicationContext、数据库的连接池等也都是单例形式。

饿汉式单例

类一加载就初始化,它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题。

优点:没有加任何锁、执行效率比较高,用户体验比懒汉式单例模式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能“占着茅坑不拉屎”。 尤其是如果一个系统有很多这样的类,一初始化就浪费很多内存。

Demo:

// 第一种写法
public class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();

    private HungrySingleton(){}

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

// 第二种写法
public class HungrySingleton2 {
    private static HungrySingleton2 instance = null;
    static {
        instance = new HungrySingleton2();
    }

    private HungrySingleton2() {}

    public static HungrySingleton2 getInstance() {
        return instance;
    }

}


懒汉式单例模式

被外部类调用的时候内部类才会加载.

优点:没有线程安全问题

缺点:使用了synchronized,存在着性能问题

Demo

public class LazySingleton2 {

    private static LazySingleton2 instance = null;

    private LazySingleton2(){}

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

        return instance;
    }
}

优化Demo2

使用双重检查锁

public class LazySingleton3 {
    private volatile static LazySingleton3 instance = null;
    private LazySingleton3(){}

    public static LazySingleton3 getInstance() {
        /**
         * 这里为什么判断了不为空
         * 如果去掉这层判断,就跟之前的synchronized方法差不多,
         * 如果线程多了,还是阻塞很多
         * 这里加了一层判断,就会少了一些阻塞
         */
        if (instance != null) {
            synchronized (LazySingleton3.class) {
                if (instance != null) {
                    instance = new LazySingleton3();
                }
            }
        }

        return instance;
    }
}

这里会在后面的文章讲下为什么会有volatile这个?

优化Demo3 使用静态内部类.

顾了内存浪费和synchronized性能问题

  • 为什么兼顾了内存浪费?
  • 因为静态内部类是要外部类调用才初始化
  • 性能问题:因为是已经初始化好了
public class LazySingleton4 {

    private LazySingleton4(){}

    public static final LazySingleton4 getInstance() {
        return inner.instance;
    }

    private static class inner {
        private static final LazySingleton4 instance = new LazySingleton4();
    }

}

反射破坏单例

// 单例类
public class Singleton {

    private static final Singleton instance = new Singleton();
    private Singleton(){
    }

    public static final Singleton getInstance() {
        return instance;
    }
}

// 测试类
public class ReflectDestroySingleton {
    public static void main(String[] args) {
        Class<Singleton> singletonClass = Singleton.class;
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = null;

        try {
            Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            instance2 = constructor.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        System.out.println(instance1 == instance2);
    }
}

上面运行结果为false,就违背了单例,如果饿汉式单例我们可以在构造方法里面做下文章.

public class Singleton {

    private static final Singleton instance = new Singleton();
    private Singleton(){
        // 防止反射破坏
        if (instance != null) {
            throw new RuntimeException("不能new了");
        }
    }

    public static final Singleton getInstance() {
        return instance;
    }
}

这样反射创建就会报错了。

序列化破坏单例

Demo

// 单例类 Serializable是序列化必须实现的接口
public class Singleton implements Serializable {

    private static final Singleton instance = new Singleton();
    private Singleton(){}

    public static final Singleton getInstance() {
        return instance;
    }
}
// 序列化破坏
public class SerializableDestroySingleton {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = null;

        try {
            FileOutputStream fileOutputStream = new FileOutputStream("instance.obj"); // 放在instance.obj,随便写
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(instance1);

            // 反序列化
            FileInputStream fileInputStream = new FileInputStream("instance.obj");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            instance2 = (Singleton) objectInputStream.readObject();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }


        System.out.println(instance1 == instance2);
    }
}

上面运行结果为false。 违背了单例,怎么解决呢?

public class Singleton implements Serializable {

    private static final Singleton instance = new Singleton();
    private Singleton(){}

    public static final Singleton getInstance() {
        return instance;
    }

    // 加上这个方法,序列化就不能破坏了 必须要这样写
    private Object readResolve() {
        return instance;
    }
}

为什么呢?加了这个方法,运行结果就为true了。 从源码去进行剖析。

我们点进readObject()方法

再点readObject0方法,翻到下面switch,我们这里是对象就看到C_OBJECT

点进readOrdinaryObject方法,

isInstantiable()方法里面是判断有没有构造方法,我们这里有所以执行了实例化。

继续往后看。

hasReadResolveMethod()这个方法就是判断有没有resovleMethod()方法,也就是我们在单例类写的那个方法。点进去看下吧。

就到这里了。 继续。

这里如果判断有那个方法,就调用那个方法

然后将那个方法返回的值,赋值给rep。
所以刚才运行结果为true,但是在内部还是实例化了一次,只不过覆盖了。

注册式单例

第一种使用枚举。
优点

  • 反射不能破坏
  • 序列化不能被破坏
  • 线程安全

不能破坏的原因,继续往下看,下面要分析。

Demo

public enum  RegisteredSingleton {
    INSTANCE;
    // 这里只是为了这个实例可以存些数据,去掉也可以
    private Object data;

    public void setData(Object data) {
        this.data = data;
    }

    public Object getData() {
        return data;
    }
}

尝试反射破坏枚举单例

public static void main(String[] args) {
        // 验证反射不能破坏枚举单例
        Class<RegisteredSingleton> singletonClass = RegisteredSingleton.class;
        /**
         * 首先看源码Enum这个类没有无参的构造,有一个有参构造  protected Enum(String name, int ordinal)
         */
        try {
            Constructor<RegisteredSingleton> constructor = singletonClass.getDeclaredConstructor(String.class, int.class);
           constructor.setAccessible(true);
            RegisteredSingleton singleton = constructor.newInstance();
            System.out.println(singleton);
            /**
             * 运行结果报错 : Cannot reflectively create enum objects
             * at java.lang.reflect.Constructor.newInstance(Constructor.java:417) 
             这行报错,点进去看
             * if ((clazz.getModifiers() & Modifier.ENUM) != 0)
             *             throw new IllegalArgumentException("Cannot reflectively create enum objects");
             *   上面这句代码已经很清楚了,有枚举修饰符的报错
             */
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        
    }

尝试序列化破坏枚举单例

public static void main(String[] args) {
        // 验证序列化破坏枚举单例
        RegisteredSingleton instance = RegisteredSingleton.INSTANCE;
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("instance.obj");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(instance);


            FileInputStream fileInputStream = new FileInputStream("instance.obj");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            RegisteredSingleton instance2 = (RegisteredSingleton) objectInputStream.readObject();
            System.out.println(instance == instance2);
            /**
             * 运行结果为true,那么为什么没被破坏呢?看下源码readObject方法
             * Enum<?> en = Enum.valueOf((Class)cl, name);
             * 底层调用的是这个
             * 再看看这个valueOf方法,底层:T result = enumType.enumConstantDirectory().get(name);
             * 他是从这个里面获取的
             *private volatile transient Map<String, T> enumConstantDirectory = null;
             */
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

第二种是容器式单例。

public class ContainerModeSingleton {
    private ContainerModeSingleton(){}

    private static Map<String,Object> map = new ConcurrentHashMap<>();

    public static Object getInstance(String className) {
        // 1.第一种写法
        synchronized (map) {
            if (!map.containsKey(className)) {
                Object obj = null;
                try {
                     obj = Class.forName(className).newInstance();
                    map.put(className,obj);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }

                return obj;
            }

            return map.get(className);
        }
    }
}