zoukankan      html  css  js  c++  java
  • Design Pattern [1] —— 单例模式 Singleton

    单例模式:一个类只能构造一个实例对象("构造器私有")

    场景:

      Windows任务管理器、回收站

      项目中,配置文件的类,一般只有一个对象

      网站的计数器、时钟

      数据库连接池

      Servlet

      Spring中的Bean(缓存中取bean很快,减少jvm垃圾回收)(当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象)

    好文:https://zhuanlan.zhihu.com/p/33102022  (本文还需要进一步根据面经深化)


     我的Github里有源码,可以clone下来自己跑下:https://github.com/Yang2199/Design-Pattern/tree/master/src

    1)饿汉式单例

      ==》上来直接new对象,所有类实例化。坏处是:大量浪费不必要的资源(因为很多类   不需要实例化)

    public class Hungry {
        private Hungry(){} //构造函数
        private final static Hungry HUNGRY = new Hungry();//直接实例化,new出对象
        public static Hungry getInstance(){
            return HUNGRY;
        }
    }

    2)懒汉式单例 

       ==》懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。

        线程不安全:多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,

        因此需要加锁解决线程同步问题( Synchronized 同步锁来修饰 getInstance 方法)

    public class Singleton {
        private Singleton() {}  //单例=》私有构造器
        private static Singleton instance = null;  //单例对象
        public static Singleton getInstance() {  //调用getInstance方法才会开始构造对象
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }

    但是这种方法不符合线程安全:

    public class Singleton {
        private Singleton() {
            System.out.println(Thread.currentThread().getName()); //构造时候打印线程名
        }  //私有构造函数
        private static Singleton instance = null;  //单例对象
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            for(int i =0;i<10;i++){
                new Thread(  ()->{Singleton.getInstance();}  ).start();  //1.启动新线程 2.lambda表达式
            }
        }
    }

    可能会出现构造了很多个的情形

    方法一:双重锁检测 DCL

      ==》首先将类加同步锁(syn),但是new语句不是原子操作,所以对了类的实例加volatile锁(可见性)

    第一把锁:synchronized锁

    public static Singleton getInstance() {
        if (instance == null) {     //外层判断
            synchronized (Singleton.class) {    //将Singleton类 加锁
                if (instance == null) {     //原有判断
                    instance = new Singleton();
              // 上面这行 不是原子操作:
              // 1.分配内存空间 2.执行构造方法,初始化对象 3.把这个对象指向这个空间 } } }
    return instance; }
    //加上两层:一层if、一层锁
    //这样就能保证单例在多线程下的唯一性
    //双重检查模式:DCL (Double Check)

    第二把锁:volatile锁

    private volatile static Singleton instance = null;  //由于instance = new Singleton()的非原子性(new对象3步),所以需要volatile保证强制同步一致

    volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值

    但是,还是可以用反射来破坏DCL:

        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            Singleton instance = Singleton.getInstance();
            Constructor<Singleton> declaredConstructor =Singleton.class.getDeclaredConstructor(null);  //通过反射获取declaredConstructor这个构造器
            declaredConstructor.setAccessible(true);
            Singleton instance2 = declaredConstructor.newInstance();
    
            System.out.println(instance.hashCode() );
            System.out.println(instance2.hashCode() );
        }

    实现单例模式,3种方法对比:


     方法二:用静态内部类

    public class Holder {
        private Holder(){}
    
        public static Holder getInstance() {return InnerClass.HOLDER;}
    
        private static class InnerClass{
            private static final Holder HOLDER = new Holder();
        }
    }

    这个方法也能被反射破坏单例的唯一性

    方法三:枚举

      ==》直接把类名前的class替换成enum就好了,因为枚举无法反射

    枚举:(通过查看源码可知,枚举时,无法反射。)

    public enum EnumSingleton { //这里是enum而不是class
        INSTANCE;
        public EnumSingleton getInstance(){
            return INSTANCE;
        }
    }

    测试:

    class Test{
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            EnumSingleton instance1 = EnumSingleton.INSTANCE.getInstance();
    Constructor
    <EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingleton instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }

    会显示:

    异常:枚举无法创造反射

    符合预期。

  • 相关阅读:
    第四周编程总结
    第三周编程总结
    第二周编程总结
    查找整数 编程总结
    求最大值及其下标 编程总结
    C语言I博客作业04
    C语言I博客作业03
    C语言I博客作业02
    作业01
    第八周作业
  • 原文地址:https://www.cnblogs.com/qyf2199/p/14597621.html
Copyright © 2011-2022 走看看