单例模式解析

329 阅读4分钟

单例模式解析

​ 单例模式是设计模式中使用最多的模式之一,它是一种对象的创建模式,用于产生一个对象的实例,确保系统中一个类只有一个实例。在Java语言中,单例模式有以下好处:

  • 对于频繁使用的对象,单例省略了创建对象的时间,对于重量级对象而言,极大减少了系统开销。
  • 由于new的次数减少,因此减少了系统对内存的使用频次,减轻了GC压力,缩短了GC停顿时间。

因此对于系统的关键组件和频繁使用的对象,可以设计为单例模式,减少系统的开销,提高系统性能。

​ 单例模式的参与者很少,只有单例类和使用者,关系表如下:

角色 作用
单例类 提供单例的工厂,返回单例
使用者 获取并使用单例

​ 类图如下:

实现方式一(饿汉):

单例模式的核心在于通过一个接口返回唯一实例化对象,简单实现如下:

/**
 * 单例模式--饿汉(无法做到延时加载)
 */
public class HungrySingleton {

    private static HungrySingleton instance = new HungrySingleton();

    /**
     * 私有构造方法
     */
    private HungrySingleton(){
        System.out.println("创建了对象");
    }

    /**
     * 对外暴露的获取唯一实例的接口
     * @return
     */
    public static HungrySingleton getInstance(){
        return instance;
    }

    /**
     * 序列化,反序列化保证单例
     * @return
     */
    private Object readResolve(){
        return instance;
    }
}

原理:单例类必须私有化构造方法,保证不会在系统其他地方调用,其次instance成员变量和getInstance()方法必须是static修饰的。

注意:单例模式的这种实现方式非常简单,十分可靠,唯一不足是无法做到延时加载。假设单例的创建过程十分缓慢,由于instance的成员变量是由static修饰的,在JVM加载单例类的时候,单例对象就会存在,如果单例类还在系统中扮演别的角色,那么系统中任何使用单例类的地方都会初始化这个单例对象,而不管是否被用到。

实现方式二(懒汉):

为了解决上述问题,并提高系统在相关函数调用的反应速度,就需要加入延时加载机制,懒汉模式。

/**
 * 单例模式--懒汉(效率低,延时加载)
 */
public class LazySingleton {

    private static LazySingleton instance = null;

    private LazySingleton() {
        System.out.println("LazySingleton is create");
    }

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

原理:首先,静态成员变量instance赋值为null,确保系统启动时没有额外负载,其次在getInstance()方法中判断当前单例instance对象是否存在,存在则返回,不存在创建单例对象。

注意:getInstance()必须是线程同步的,否则在多线程条件下,当线程1新建单例完成赋值前,线程2可能判断instance为null,线程2也创建了单例对象,导致多个实例被创建,因此同步关键字是必须的。使用上述单例模式的实现方式,虽然实现了延时加载,但是和第一种实现(饿汉)相比,引入了同步关键字,因此在多线程场景下,加载速度远远大于第一种实现方式,影响系统性能。

实现方式三(内部类):

继续改进,创建内部类:

/**
 * 使用内部类来维护单例的实例,当StaticSingleton被加载时候,内部类并没有被初始化
 * (instance并没有被初始化),调用getInstance()才会被初始化。
 */
public class StaticSingleton {

    private StaticSingleton(){
        System.out.println("StaticSingleton is create");
    }

    /**
     * 内部类,创建单例对象
     */
    private static class StaticSingleHolder{
        private static StaticSingleton instance = new StaticSingleton();
    }

    public static StaticSingleton getInstance(){
        return StaticSingleHolder.instance;
    }
}

原理:在这个实现中,使用内部类来维护单例实例,当StaticSingleton被加载的时候,内部类没有被初始化,可以确保StaticSingleton加载到JVM中,不会初始化单例类,当调用getInstance()时才会加载StaticSingleHolder,初始化instance对象,同时由于实例的建立是在类加载时完成的,对线程友好,getInstance()不需要使用同步关键字。

注意:使用内部类实现的单例,既可以实现延时加载也避免使用同步关键字,是比较完善的实现。但是如果通过反射机制强行调用私有构造方法,就会生成多个单例。同时序列化和反序列化可能破坏单例(饿汉代码readResolve()方法),场景不多见,如果存在,多加注意。

[^《Java性能程序优化 让你的Java程序更快、更稳定》 葛一鸣]