zoukankan      html  css  js  c++  java
  • 单例模式

    单例模式(Singleton),是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。

    单例模式一般符合以下三个条件:

    • 私有的构造方法;

    • 指向自己实例的私有静态引用;

    • 以自己实例为返回值的静态的公有方法。

    单例一般具有以下几种实现方法

    1、饿汉式(立即加载)

    // 饿汉式单例
    public class Singleton {
     
        // 指向自己实例的私有静态引用,主动创建
        private static Singleton singleton = new Singleton();
     
        // 私有的构造方法
        private Singleton(){}
     
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static Singleton getSingleton(){
            return singleton;
        }
    }

    这种方法是类被加载时候,就实例化一个对象并交给自己的引用,即使在多线程下也是线程安全的,因为类只会被加载一次,缺点就是资源效率不高,可能getInstance()永远不会执行到。

    2、懒汉式(延迟加载)

    // 懒汉式单例
    public class Singleton {
     
        // 指向自己实例的私有静态引用
        private static Singleton singleton;
     
        // 私有的构造方法
        private Singleton(){}
     
        // 以自己实例为返回值的静态的公有方法,静态工厂方法
        public static Singleton getSingleton(){
            // 被动创建,在真正需要使用时才去创建
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    这种方法资源利用率高,不执行getInstance()就不会被实例,但是在多线程下,该方法存在线程安全问题。

    3、线程安全的懒汉式

    // 线程安全的懒汉式单例
    public class Singleton {
     
        private static Singleton singleton;
     
        private Singleton(){}
     
        // 使用 synchronized 修饰,临界资源的同步互斥访问
        public static synchronized Singleton getSingleton(){
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    该实现与上面传统懒汉式单例的实现唯一的差别就在于:是否使用 synchronized 修饰 getSingleton()方法。若使用,就保证了对临界资源的同步互斥访问,也就保证了单例。从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗。同步方法效率低。

    4、改进的线程安全的懒汉式

    // 线程安全的懒汉式单例
    public class Singleton {
     
        //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
        private static volatile Singleton singleton;
     
        private Singleton() {
        }
        public static Singleton getSingleton() {
            // Double-Check idiom
            if (singleton == null) {
                synchronized (Singleton.class) {       // 1
                    // 只需在第一次创建实例时才同步
                    if (singleton == null) {       // 2
                        singleton = new Singleton();      // 3
                    }
                }
            }
            return singleton3;
        }
    }

    使用双重检查保证了只有第一次创建的时候才会进入同步块,之后获取单例的时候不需要再去获取同步锁。但是这里要特别注意必须使用volatile关键字修饰单例引用。

    因为
    做第一次检查的时候,如果singleton!= null,并不代表singleton一定已经初始化完成了,造成这种情形的原因是指令重排序;

    singleton = new Singleton(); 这句在底层经历了3个动作:

    1.分配内存给这个对象;
    2.初始化对象;
    3.设置 singleton 指向刚分配的内存;
    这3个动作中,2和3的动作可能颠倒,其造成的结果就是:Thread-0第一次检查的时候,由于Thread-1先执行3,singleton 指向刚分配的内存,导致Thread-0看到的 singleton 不为空,直接返回 singleton,但此时singleton 在Thread-1中还没有初始化,所以造成程序出问题;

    用 volatile 修饰 lazyDoubleCheckSingleton,就禁止了重排序;

    5、静态内部类(延迟加载

    // 线程安全的懒汉式单例
    public class Singleton5 {
     
        // 私有内部类,按需加载,用时加载,也就是延迟加载
        private static class Holder {
            private static Singleton5 singleton5 = new Singleton5();
        }
     
        private Singleton5() {
     
        }
     
        public static Singleton5 getSingleton5() {
            return Holder.singleton5;
        }
    }

    该方法利用了JVM加载一个类时,其内部类不会同时被加载。达到了延迟加载的目的,又不会由线程安全问题

    6、使用枚举

    public enum  EnumSingleton {
        INSTANCE;
        public EnumSingleton getInstance(){
            return INSTANCE;
        }
    }

    上面的五种方法都可以利用反射实例化出另一个实例,存在安全问题,使用枚举则不会。

  • 相关阅读:
    动态规划专题选做
    「HZOJ NOIP2020 Round #13」20201127模拟 题解
    「HZOJ NOIP2020 Round #12」20201124模拟 简要题解
    JOI 2019 Final 硬币收藏 第18回日本情報オリンピック 本選 コイン集め 解説
    0202S-SCP 收容记
    NC50993 The XOR Largest Pair 0-1Trie Xor
    LG3120 [USACO15FEB]Cow Hopscotch G CDQ分治维护DP顺序
    2020牛客NOIP赛前集训营-提高组(第二场)
    「HZOJ NOIP2020 Round #5」20201018 模拟
    关于我
  • 原文地址:https://www.cnblogs.com/pjfmeng/p/10896475.html
Copyright © 2011-2022 走看看