「设计模式」单例模式这一篇就够了

276 阅读5分钟

概述

单例模式(Singleton Pattern)是 Java 中最简单(不简单)的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。0这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

实现

单例模式实现的四种方式:饿汉、懒汉、静态内部类、枚举

饿汉

由于使用了 static 关键字,所以类加载时就会创建单例对象
优点: 避免了多线程下线程安全的问题(只会在类加载时创建一次)
缺点: 在类加载时就创建了对象,会出现如果用不上该对象从而浪费内存空间的情况

public class Instance {

    /**
     * 也可以吧创建对象放在static静态块中
     */
    private static Instance instance = new Instance();

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 获取实例
     *
     * @return
     */
    public static Instance getInstance() {
        return instance;
    }
}

懒汉-非线程安全

创建时机是第一次被调用,延时加载
优点: 需要的时候才会创建实例,节省内存
缺点: 多线程并发获取单例对象的情况下回创建多个对象,线程不安全

public class Instance {

    private static Instance instance;

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 获取实例
     *
     * @return
     */
    public static Instance getInstance() {
        if (null == instance) {
            instance = new Instance();
        }
        return instance;
    }
}

懒汉-线程安全

添加 synchronized 来保证线程安全
优点: 需要的时候才会创建实例,节省内存
缺点: 解决了线程安全问题,但是在高并发的情况下性能不高

public class Instance {

    private static Instance instance;

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 使用synchronized关键字对方法加标记,串行调用
     * 获取实例
     *
     * @return
     */
    public static synchronized Instance getInstance() {
        if (null == instance) {
            instance = new Instance();
        }
        return instance;
    }
}

懒汉-双重检查锁机制

使用双重检查锁避免过多的同步,第一次的检查是无锁的,如果对象已经创建则直接返回了。
重点在于第二次的同步锁校验和 volatile 关键字,Java 中 new Instance()并不是一条原子指令,通过 javap -c 查看字节码时可以看到创建对象一共分了三个步骤:
1.给分配对象内存
2.调用构造器方法,执行初始化
3.将对象引用赋值给变量
为什么要使用 volatile:
当 JVM 实际运行时,又有可能会对 2 和 3 指令进行重排序(因为 2 和 3 依赖于 1 所以 1 不会重排),指令重排不了解的自行百度,当有两个线程 A、B 同时调用获取实例方法时,第一重判断 ① 都判断为空,同时进入到获取同步锁 ② 阶段,假设有 A 获取到锁,B 线程则阻塞等待,A 线程则去创建对象后释放锁,由于创建对象需要经过以上三个步骤,假设 new Instance()执行到第 3 个步骤时(还未执行第 2 个步骤),由于锁已经释放,B 线程获取锁执行第二重判断 ③,这时对象已经不为空(已经分配了内存)了,则可以自由访问该对象,然而这时对象并没有被初始化(A 线程还未执行第 2 个步骤),这个时候 B 线程使用该对象时则会出现异常,使用 volatile 关键字可以保证可见性和防止指令重排序。
优点: 需要的时候才会创建实例,节省内存,锁粒度比较小
缺点: 在某种程度上来说解决了性能问题,但是使用时需要了解对象创建的过程和 volatile 关键字原理,另外 Java1.4 及以前版本中,很多 JVM 对于 volatile 关键字的实现有问题,会导致双重检查加锁的失败。

public class Instance {

    /**
     * 使用volatile关键字修饰
     * 保证可见性和防止指令重排序
     */
    private volatile static Instance instance;

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 双重检查锁
     * 获取实例
     *
     * @return
     */
    public static Instance getInstance() {
        // 第一重判断 ①
        if (null == instance) {
            // 获取同步锁 ②
            synchronized (Instance.class) {
                // 第二重判断 ③
                if (null == instance) {
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

静态内部类(推荐使用)

静态内部类实现单例某种意义上来说也是数据懒汉类,实现了延时加载的同时也没有线程安全性的问题
优点: 外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化 instance,故而不占内存。
缺点: 第一次创建对象时需要重新加载子类,在某种程度上影响了执行效率

public class Instance {

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 使用静态内部类获取
     *
     * @return
     */
    public static Instance getInstance() {
        return InstanceHolder.instance;
    }

    private static class InstanceHolder {
        /**
         * 内部类持有外部类对象
         */
        static Instance instance = new Instance();
    }
}

枚举类

枚举类实现单例模式是 effective java 作者极力推荐的单例实现模式。
优点: 枚举类型是线程安全的,并且只会装载一次、天然支持防止反序列化重新创建新的对象。
缺点: 某种意义上来说我没有用过,手动狗头保命

public enum Instance {
    INSTANCE;

    /**
     * 添加业务操作
     */
    public void toDo() {
    }
}

本期就到这啦,有不对的地方欢迎好哥哥们评论区留言,另外求关注、求点赞