记录一下对于单例模式的重新认识

单例模式,毋庸置疑,(这里以java语言来描述)字面意思就是在一个java进程中,只会存在一个对象实例。

单例模式主要分为4种,饿汉式,懒汉式,委托,枚举;

1.饿汉式:

/**
 *
 * 测试饿汉式
 * 能够保证线程的安全性,但是没有延迟加载,实例化完成后,
 * 如果长时间不进行使用,会造成内存空间的浪费,因为单例模式的实例中如果包含很多的静态成员变量
 * 占用内存比较大,则内存空间的浪费就会非常明显。所以如果提高其性能,需要进行懒加载
 *
 * @date 2020-01-01
 * @since
 */
public class HungryDemo {

    private static byte[] bytes = new byte[1024];

    public static String str = "just test";

    static {
        System.out.println("hungry singleton");
    }

    /**
     * 定义私有的构造器
     */
    private HungryDemo() {}


    /**
     * HungryDemo中的静态变量在类加载期间就会初始化,
     * 加载 => classLoader将类字节码加载到jvm内存中
     * 验证 => 验证字节码文件的正确性
     * 准备 => 为静态变量赋予初始值
     * 解析 => 将符号引用转变为直接引用
     * 初始化 => 初始化静态代码块(静态变量和静态代码块最后编译后会统计到静态代码块中)和全局变量值,在<clinit>方法中执行
     * 此时的HungryDemo会被初始化,因为涉及到创建对象,便会去JVM中查找因此进行对象的创建(此时的class对象已经创建完毕),
     * 然后调用<init>(构造器函数)完成对象的创建。
     *
     */
    private static HungryDemo instance = new HungryDemo();

    /**
     * 调用此方法时才会触发类加载
     * 首先会调用jvm的方法区中查找是否存在HungryDemo的类信息(class对象信息)
     * 不存在,则调用classloader来加载HungryDemo进行类加载,然后就是后面的类加载的过程了。
     */
    public static HungryDemo getInstance() {
        return instance;
    }

}

2. 懒汉式:

/**
 * 测试懒汉式, 记住,
 * 懒汉式的实例中一般都包含很多的成员变量
 *
 * @date 2020-01-01
 * @since
 */
public class LazyDemo {

    public static String name = "lazy singleton";

    private volatile static LazyDemo instance;

    static {
        System.out.println("lazy singleton");
    }

    private LazyDemo() {}

    public static LazyDemo getInstance() {
        return instance;
    }

    /**
     * 这样处理在多线程的情况下会造成多实例的错误
     * 会出现线程安全的问题
     *
     * @return
     */
    public static LazyDemo getInstance1() {
        if (Objects.isNull(instance)) {
            instance = new LazyDemo();
        }

        return instance;
    }

    public static LazyDemo getInstance2() {
        // 多线程调用会变为重量级锁,性能低下
        synchronized (LazyDemo.class) {
            if (Objects.isNull(instance)) {
                instance = new LazyDemo();
            }
        }

        return instance;
    }

    /**
     * 保证了懒加载,和线程安全,但是效率不高
     *
     * @date 2020-01-01
     * @updateDate 2020-01-01
     * @param
     * @return
     */
    public static LazyDemo getInstance3() {
        // DCL,和上一个方法对比,不为空则直接获取实例,不会出现串行的情况,提升了性能。
        if (Objects.isNull(instance)) {
            synchronized (LazyDemo.class) {
                // 这里在做一次判断,是为了保证2个线程同时进入了第一个判断代码块内
                if (Objects.isNull(instance)) {
                    // 创建一个对象,编译成字节码后,创建对象分为三部分执行
                    // 23: new           #4                  // class com/feifei/demo/singleton/LazyDemo 申请内存空间
                    // 26: dup // 复制栈顶元素并压入栈顶
                    // 27: invokespecial #5                  // Method "<init>":()V // 执行构造器相关的方法
                    // 30: putstatic     #2                  // Field instance:Lcom/feifei/demo/singleton/LazyDemo; // 将变量压入常量池中
                    // 这26, 27, 30可能会发生指令重排的问题,也就是30 在 27执行执行,然后此时线程2进入方法直接返回对象,但是此时对象还未执行构造器初始化,造成对象没有达到安全发布
                    // 所以需要加入volatile关键字,让对象创建期间不会发生指令重排的问题
                    instance = new LazyDemo();
                }
            }
        }
        return instance;
    }
}

3. 委托给静态内部类:

/**
 * 构造模式
 *
 * 声明类的时候,成员变量中不声明实例变量,而是放到内部的静态类中
 * 将实例变量委托给静态内部类,当主动去调用getInstance的时候,
 * 才会创建对象,实现了懒加载
 *
 * 这是目前比较优雅的方法,即没有用到如何的锁机制,也不需要担心线程安全性的问题
 *
 * @date 2020-01-01
 * @since
 */
public class HolderDemo {

    public static String name = "holder singleton name";

    static {
        System.out.println("holder singleton");
    }

    private HolderDemo() {

    }

    /**
     * 定义一个静态内部类, 外部类的类加载并不会影响到内部类的加载,
     * 只要外部类的变量没有涉及到内部类的创建即可
     */
    private static class InnerHolder {

        static {
            System.out.println("inner holder singleton");
        }
        private static HolderDemo instance = new HolderDemo();
    }

    public static HolderDemo getInstance() {
        // 此时才会触发静态内部类的类加载已经对象的创建
        return InnerHolder.instance;
    }
}

4. 枚举

/**
 * 枚举测试,枚举天生就是一个单例
 *
 * @date 2020-01-01
 * @since
 */
public enum EnumDemo {

    /**
     * 单例对象引用
     */
    ENUM_DEMO;

    private static String name = "enum singleton";

    EnumDemo() {}

    public static EnumDemo getInstance() {
        return EnumDemo.ENUM_DEMO;
    }
}

最后,我们编写一个测试类Test:

/**
 * @date 2020-01-01
 * @since
 */
public class Test {

    public static void main(String[] args) {
        // 此时的类初始化了,类加载后会导致instance实例的创建
        System.out.println(HungryDemo.str);
        System.out.println(HungryDemo.getInstance());

        // 此时类初始化了,但是并没有对instance的实例进行创建
        System.out.println(LazyDemo.name);
        System.out.println(LazyDemo.getInstance());
        // 调用此方法才会造成实例的创建,这才是真正的懒加载
        System.out.println(LazyDemo.getInstance3());

        // 采用委托给静态内部类来完成懒加载,其内部类并不会触发类加载
        System.out.println(HolderDemo.name);
        // 此时才会触发内部类的类加载和实例的创建
        System.out.println(HolderDemo.getInstance());

    }
}

如果针对类型的区分推荐采用枚举类,如果涉及到单例的使用,建议使用委托给内部类来实现的构造模式来实现。

如有疑问,可留言。

发布了40 篇原创文章 · 获赞 17 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_38796327/article/details/103792963
今日推荐