• 单例模式


    单例模式

    注意:

    • 1、单例类只能有一个实例。

    • 2、单例类必须自己创建自己的唯一实例。

    • 3、单例类必须给所有其他对象提供这一实例。

    考查的核心知识点:

    1. 线程安全
    2. 内存模型
    3. 类加载机制

    1.单例模式是什么?为什么需要单例模式?

    单例模式 : 顾名思义, 就是在整个运行时域(runtime), 一个类只有一个实例对象.

    为什么需要单例模式 : 有的类的创建和销毁对资源来说消耗不大, 比如String; 有的类就比较庞大和复杂. 如果频繁的创建和销毁这些对象, 并且这些对象是完全可以复用的情况下, 将会造成不必要的性能浪费.

    例子:

    创建一个数据库的链接对象, 只需用单例模式创建一次就OK.

    2.如何实现单例模式?

    多种写法,多种思维.

    要实现单例模式, 主要考虑3点:

    1. 是否线程安全.
    2. 是否是懒加载.(Lazy loading)
    3. 是否能够通过反射破坏.

    3.实例:

    3.1 懒汉式:(线程不安全的)

    public class Singleton {
        
        private Singleton() {
            // 构造器私有--1
        }
        // 初始化对象为null
        private static Singleton instance = null;
        // 通过getInstance()方法来使用Singleton对象
        public static Singleton getIntance() {
            // 判断instance是否被构造过.
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
        
    }
    

    1.外部无法 Singleton s = new Singleton();

    懒加载:

    实例对象是第一次被调用的时候才真正构建, 而不是程序一启动就构建好等你调用. 这种滞后的加载就是懒加载.

    3.2 懒汉式:(线程安全的)

    public class Singleton {
        public Singleton() {}
        public static Singleton instance = null;
        public static synchronized getIntance() {
            if(instance == null){
                Singleton s = new Singleton();
            }
            return instance;
        }
    }
    

    这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低. 每次获取对象是都要进行同步操作, 对性能影响非常大.

    3.3 饿汉式:

    public void Singleton {
        // 编译期构建
     	private static Singleton instance = new Singleton();
        private Singleton(){}
        public static Singleton getInstance() {
            return instance;
        }
    }
    

    描述:这种方式比较常用,但容易产生垃圾对象。
    优点:没有加锁,执行效率会提高。
    缺点:类加载时就初始化,浪费内存。

    拓展:

    对象在类中被定义为private static,通过getInstance(),通过java的classLoader机制保证了单例对象唯一。

    3.4 双检锁(DCL,即 double-checked locking)

    有没有即是线程安全, 又是懒加载的单例模式, 双检锁就出现了.

    改造 懒汉式:(线程安全的)

    public class Singleton {  
        private static Singleton singleton;  
        private Singleton (){}  
        public static Singleton getSingleton() {  
        if (singleton == null) {  
            
            // 调用时构建,if里有多个线程,a执行完后,b也执行.导致重复构建.
            synchronized (Singleton.class) {
                singleton = new Singleton();  
            }  
        }  
        return singleton;  
        }  
    }
    

    使用双if:

    public class Singleton {
        private static Singleton singleton;
        private Singleton() {}
        public static Singleton getSingleton() {
            if(singleton == null) {
                synchronized (Singleton.class) {
                    if(singleton == null) {
                        singleton = new Singleton(); 
                    }
                }  
            }
            return singleton;
        }
    }
    //还会有一个指令重排序问题.
    

    为什么要使用volatile修饰?

    虽然已经使用synchronized进行同步,但在创建singleton对象时,会有下面的伪代码:

    memory=allocate(); // 1:分配对象的内存空间
    ctorInstance();   // 2: 初始化对象
    singleton=memory; // 3: 设置instance指向刚分配的内存地址
    

    当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以JVM是允许的。

    如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!

    使用了volatile就能阻止作用在instance上的指令重排问题.

    public class Singleton {
        private volatile static Singleton singleton;
        private Singleton() {}
        public static Singleton getSingleton() {
            if(singleton == null) {
                syschornized (Singleton.class) {
                    if(singleton == null) {
                        singleton = new Singleton(); 
                    }
                }  
            }
            return singleton;
        }
    }
    

    3.5 静态内部类

    在程序启动是不会加载, 只有在第一调用时才会加载.

    public class Singleton {  
        private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
        }  
    }
    
    //线程安全的,懒加载.
    

    描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

    3.6 枚举

    不能通过反射破坏

    无法满足懒加载

    自动避免序列化/反序列化攻击

    public enum Singleton {  
        INSTANCE;    
    }
    
  • 相关阅读:
    CSU-ACM集训-模板-主席树
    Codeforces- Educational Codeforces Round 69
    Codeforces-Round#574 Div2
    CF-1183C Computer Game
    CSU-ACM2019暑假训练(2)
    CSU-ACM2019暑假集训(1)
    2019牛客网第二场-F题
    洛谷P1111 修复公路
    求强连通分量-korasaju算法
    并查集-路径优化+秩优化
  • 原文地址:https://www.cnblogs.com/gzp5608/p/13784113.html
Copyright © 2020-2023  润新知