设计模式之单例模式(Singleton)

首先是懒汉式:指全局的单例实例在第一次被使用时构建。按需加载,资源最大化利用


/**
 * 懒汉式单例
 *
 * @author lvtong
 * @date 2019/11/27
 */
public class Singleton1 {

    /**
     * TODO 4:volatile关键字禁止指令重排
     */
    private static volatile Singleton1 instance;

    /**
     * TODO 1:构造器私有
     * 把构造器改为私有的,这样能够防止被外部的类调用。
     */
    private Singleton1() {
    }

    /**
     * 每次获取instance之前先进行判断,如果instance为空就new一个出来,否则就直接返回已存在的instance
     * 但多线程时会出问题,多个线程同时判断到instance=null,则会new出多个实例
     * TODO 2:加锁:
     * 避免了可能出现因为多线程导致多个实例的情况
     * 但是加了锁,其他线程要访问就要一直等待,执行效率会下降
     * 为了1%的概率事件,而采用100%防护
     */
//    public static synchronized  Singleton1 getInstance() {
//        if (instance == null) {
//            instance = new Singleton1();
//        }
//        return instance;
//    }

    /**
     * TODO 3:双重检查
     * 第一层判断:只有当instance为空时才加锁
     * 第二层判断:防止出现多个实例
     *
     * @return 单例
     */
    public static Singleton1 getInstance() {
        if (instance == null) {
            synchronized (Singleton1.class) {
                if (instance == null) {
                    instance = new Singleton1();
                }
            }
        }
        return instance;
    }
    /**
     * 原子操作?
     * 简单来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。
     * 这样,在多线程中,由于线程执行顺序的不确定性,如果两个线程都使用m,就可能会导致不稳定的结果出现。
     * 指令重排?
     * 计算机为了提高执行效率,会做的一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。
     * 由于以上两种操作的存在
     * 对于instance = new Singleton1()这条语句
     * 1. 给instance分配内存
     * 2. 调用instance的构造函数来初始化成员变量,形成实例
     * 3. 将instance对象指向分配的内存空间(执行完这步instance才是非null了)
     * 由于指令重排,所以存在instance已经不为null但是仍没有完成初始化的中间状态
     * 此时如果其他线程刚好运行到第一层判断,由于instance不为null,直接拿去用了,然后就会报错
     * 解决方案是:只需要给instance的声明加上volatile关键字即可
     * 在它的赋值完成之前,就不用会调用读操作。
     */
}

经过以上四次完善,懒汉式已经完美了。

接下来是饿汉式,指全局的单例实例在类装载时构建。

由于类装载的过程是由类加载器(ClassLoader)来执行的,这个过程也是由JVM来保证同步的,所以这种方式先天就有一个优势——能够免疫许多由多线程引起的问题。

/**
 * 饿汉式单例
 *
 * @author lvtong
 * @date 2019/11/27
 */
public class Singleton2 {

    //最基础的写法
//    private static final Singleton2 INSTANCE = new Singleton2();
//
//    private Singleton2() {
//    }
//
//    public static Singleton2 getInstance() {
//
//        return INSTANCE;
//
//    }
    //

    /**
     * TODO 改进方法一:静态内部类
     * 对于内部类SingletonHolder,它是一个饿汉式的单例实现,在SingletonHolder初始化的时候会由ClassLoader来保证同步,使INSTANCE是一个真·单例。
     * 同时,由于SingletonHolder是一个内部类,只在外部类的Singleton的getInstance()中被使用,所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。
     * 它利用了ClassLoader来保证了同步,同时又能让开发者控制类加载的时机。
     * 从内部看是一个饿汉式的单例,但是从外部看来,又的确是懒汉式的实现。
     */

    private static class SingletonHolder {

        private static final Singleton2 INSTANCE = new Singleton2();

    }

    private Singleton2() {
    }

    public static final Singleton2 getInstance() {

        return SingletonHolder.INSTANCE;

    }

    /**
     * TODO 改进方法二:枚举
     * 由于创建枚举实例的过程是线程安全的,所以这种写法也没有同步的问题。
     * 优点:足够简单又能解决大部分为题
     * 缺点是需要继承的场景不适用
     */
    public enum SingleInstance {

        INSTANCE;

        public void fun1() {
            // do something
        }

    }
    // 使用
    SingleInstance.INSTANCE.fun1();
}

两种单例模式一步步完善。感慨平时的自己写的还是不行呀。平时只是用用教科书上的懒汉式单例,从来不考虑多线程的情况,还是要好好学习呀。

你可能感兴趣的:(Android知识)