设计模式--单例模式

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

一、概念

  单例模式是这样一种概念:该类对象在当前的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;
    }
}
原文地址:https://www.cnblogs.com/lcplcpjava/p/6723911.html