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

    单例模式是比较常见的一种模式,下面简单地进行单例模式的总结。

    一、概念

      单例模式是这样一种概念:该类对象在当前的app中只有唯一一个,而且该对象是全局性的,可以被所有对象访问到。单例模式其实是非常简单的模式,它只要保证我们的系统只是初始化该类对象一次即可,废话不多说,接着下面;

    二、如何创建单例?

      有单例的概念,我们知道,单例对象必须只能被创建一次,那么,该如何保证只能被创建一次呢?

      显然,既然只有一个对象,那么对象应该是static的;

      同时为了保证这对象的只读性,我们要将它设置为private,通过get方法获取该实例;

      另外,要保证单例只是被创建一次,static修饰是不能保证的,所以我们只有将构造方法设置为私有才能保证这点,然而私有的构造方法只能被类内部方法访问,所有我们的单例对象的引用只能被该类所持有(具体看代码);

      具体看下面的示例代码(分为饿汉和懒汉模式,前者是类加载时就初始化单例,后者则在调用get方法时初始化):

      饿汉模式创建单例例子

    class SingletonDemo1{
        //保证对象只是被new一次,我们需要将构造方法私有化
        private SingletonDemo1(){
            
        }
        //构造方法私有化之后,其他类是无法通过new该对象的(暂时不考虑反射),这时,只能在该类本身设置引用变量,指向该单例
        //另外,要保证可读性和唯一性,需要以private和static修饰
        private static SingletonDemo1 instance = new SingletonDemo1();//加载类时直接初始化,这种模式称为饿汉模式
        //提供一个访问单例的全局入口get方法
        public static SingletonDemo1 getInstance(){
            return instance;
        }
    }

    上面的懒汉模式中,在类加载时变初始化了对象。有时候,我们不希望对象过早加载(有可能该对象所需要的空间资源较多),想在正真使用时才加载(当然,这样第一次加载时响应速度肯定比不上饿汉模式的),这种模式称为懒汉模式,上代码例子:

    //懒汉模式
    class SingletonDemo2{
            private SingletonDemo2(){
                
            }
            private static SingletonDemo2 instance = null;//懒汉模式在类加载时不初始化单例
            public static SingletonDemo2 getInstance(){
                //懒汉模式在正真要用单例的时候初始化单例
                if(instance==null){
                    instance = new SingletonDemo2();
                }
                return instance;
            }
    }

    总的来说,单例模式的确是没什么难度的。但是,我们上面的程序并没有考虑多线程的并发问题,在并发时,要保证单例类的构造方法只是被执行一次,上面的代码是不行的,这是,就需要将单例进行同步了。

    三、多线程下的单例模式

      显然,在饿汉模式下,并没有并发问题存在,因为构造方法是在类加载的时候就执行了,所以,下面的讨论只是针对懒汉模式进行;在懒汉模式中,如果存在多条线程同时访问getInstance的情形,则有可能在第一条线程访问到if判断语句时,系统调度了另外一条线程进行单例初始化(假设单例未初始化),那么就会出现单例被多次初始化的现象;

      为了避免并发带来的问题,很容易想到下面的同步手段:

      

    //多线程懒汉模式
    class SingletonDemo3{
        private SingletonDemo3(){
            
        }
        private static SingletonDemo3 instance = null;
        public static SingletonDemo3 getInstance(){
            //初始化前先对单例进行上锁操作
            synchronized(instance){
                if(instance==null){
                    instance = new SingletonDemo3();
                }
            }
            return instance;
        }
    }

    上面代码解决了多线程下单例初始化的问题。

    然而,我们发现,大多数时候,我们并不需要对对象进行同步操作,同步一般只需在单例初次初始化时就可以了,所以,我们可以进行一下改进(该方法对对象进行两次是否为null的判断,具体看代码注释):

    //多线程懒汉模式改进版本
    class SingletonDemo4{
        private SingletonDemo4(){
            
        }
        private static SingletonDemo4 instance = null;//懒汉模式在类加载时不初始化单例
        public static SingletonDemo4 getInstance(){
            //上锁前先判断是否为null
            if(instance==null){
                //初始化前先对单例进行上锁操作
                synchronized(instance){
                    //由于再上锁和if语句之间的空隙时间,有可能某条线程对单例进行了初始化,所以需要再次判断单例是否被初始化
                    if(instance==null){
                        instance = new SingletonDemo4();
                    }
                }
            }
            return instance;
        }
    }

    到这里,我们就可以保证单例模式的成功而且保证了性能。

    当然,其实单例模式中构造方法私有化并不能一定保证对象被创建一次的,因为通过反射还是可以创建对象的,下面加单讨论下如何防止单例中反射的多次创建对象的为题。

    四、防止反射机制破坏单例

      其实防止反射破坏单例的方法挺简单的,在正式介绍如何实现之前,我们要先理解反射创建对象的原理是什么:其实反射创建对象只是在运行时,通过class对象的方法调用该类的无参构造方法罢了。明白这点就好办了,我们只需要组装class的方法调用构造方法即可,最简单的就是在构造方法中抛出异常即可,下面是简单的代码示例:

    //防止反射的单例模式
    class SingletonDemo5{
        private SingletonDemo5(){
            //判断单例是否为null
            if(instance!=null){
                throw new RuntimeException("单例不可多次初始化!");
            }
        }
        private static SingletonDemo5 instance = null;//懒汉模式在类加载时不初始化单例
        public static SingletonDemo5 getInstance(){
            //上锁前先判断是否为null
            if(instance==null){
                //初始化前先对单例进行上锁操作
                synchronized(instance){
                    //由于再上锁和if语句之间的空隙时间,有可能某条线程对单例进行了初始化,所以需要再次判断单例是否被初始化
                    if(instance==null){
                        instance = new SingletonDemo5();
                    }
                }
            }
            return instance;
        }
    }
  • 相关阅读:
    面试题39:二叉树的深度、判断二叉树是不是平衡
    Bridge 桥接
    Adapter 适配器
    search_request.go
    scoring_criteria.go
    index_init_oprions.go
    index.go
    engine_init_options.go
    document_index_data.go
    util.go
  • 原文地址:https://www.cnblogs.com/lcplcpjava/p/6723911.html
Copyright © 2011-2022 走看看