理解设计模式之单例模式(一)

目录

定义

单例的作用

生活中的单例

代码示例

单例分类


定义

系统中某个对象有且只有一个实例,当这个实例创建之后我们不能也无法再次创建这个对象的实例,所有的操作只能基于这个唯一的实例。

单例的作用

使用单例模式的最大意义在于,确保不会针对一个对象创建多个实例,保证对象引用的唯一性和一致性。

生活中的单例

windows系统中的任务管理器,我们每次调用系统任务管理器的时候无论敲多少次ctorl+alt+del,始终只有一个任务管理器界面,再比如大学学校的班级指导员,每个班有且只有一个指导员,不可能存在两个指导员同时管理班级工作。

代码示例

常用的方式

/***
     * 常用方式,多线程模式下存在安全隐患
     * @return
     */
    public static SingleInstance getSingleInstance(){
        if (singleInstance==null){
            return new SingleInstance();
        }
        return singleInstance;
    }

这种方式在多线程编程中存在安全问题,比如有两个线程A和线程B,两个线程都需要获取SingleInstance实例,如果A线程运行完singleInstance==null的判断语句但是还没有完成实例化时线程B也执行到这里,这个时候singleInstance还是null,就会出现线程A和线程B都创建了SingleInstance实例的情况,这就违反了单例模式的设计初衷。

解决上面的问题有两种方案:饿汉单例和懒汉单例。

单例分类

饿汉单例

饿汉单例是实现起来最简单的方式,其代码如下:

private static SingleInstance singleInstance = new SingleInstance();
    private SingleInstance(){

    }
 /***
     * 饿汉模式
     * @return
     */
    public static SingleInstance getInstance() {
        return singleInstance;
    }

从代码可以看出,饿汉模式在类加载的时候就已经对SingleInstance的实例进行了初始化,当我们调用getInstance()的时候就会把预先初始化的实例返回给调用者。

懒汉单例

懒汉单例模式主要从同步锁的角度来解决普通单例模式在多线程编程中存在的问题,根据使用锁的位置不同可以分为三个版本。

版本1 在getInstance()方法上添加方法级的同步锁,代码如下:

private static SingleInstance singleInstance2 = null;
/***
     * 懒汉模式1
     * @return
     */
    public synchronized static  SingleInstance getInstance(){
        if (singleInstance2==null){
            return new SingleInstance();
        }
        return singleInstance2;
    }

这种模式在方法级别通过添加锁来解决线程安全问题,但是每次调用getInstance()都要进行线程锁定判断,高并发情况下会有比较大的性能消耗。

版本2 在版本1的基础上稍加修改,既然版本1中每次都要在方法级别进行线程锁判定,我们可以把线程锁移到getInstance方法的代码块中使用同步代码块替代方法级别的线程锁。代码如下:

 /***
     * 懒汉模式2,方法内部使用同步代码块
     * @return
     */
    public static  SingleInstance getInstance(){
        if (singleInstance2==null){
            synchronized (SingleInstance.class){
                return new SingleInstance();
            }

        }
        return singleInstance2;
    }

这种方式看上去已经很完美了,然而依然会出现上文的线程安全问题,原因是类似的。当A、B两个线程都执行完singleInstance2==null这个判断时,如果A线程比B线程先执行后面创建实例的逻辑,由于添加了锁,此时B线程是处于排队等候A释放锁的状态。等A执行完了创建实例的过程之后,锁被释放,B线程开始继续执行,这时候B线程已经判断了singleInstance2==null,并且不知道singleInstance2已经被A线程实例化了,所以B线程会再次实例化一个singleInstance2,这种情况同样违反了单例模式的单一实例原则。

终极方案:基于版本2 在其基础上进行改进,在版本2的同步代码块内部再次判断singleInstance2是否为空,代码如下:

 /***
     * 懒汉模式3,方法内部使用同步代码块,添加双重判断
     * @return
     */
    public static  SingleInstance getInstance(){
        if (singleInstance2==null){
            synchronized (SingleInstance.class){
                if (singleInstance2==null){
                    return new SingleInstance();
                }
                
            }

        }
        return singleInstance2;
    }

当B线程获取对象锁然后开始准备创建实例的时候先判断一下singleInstance2是否为空,如果singleInstance2不为空,说明已经被实例化,就不再执行实例化的操作了。

其实无论是饿汉单例还是懒汉单例,都有一些不足的地方,下一篇文章我会说明一下这两种方式存在的不足以及可以优化改进的地方

上一篇:C++的四种cast(显示类型转换)


下一篇:JS 浏览器BOM-->onresize方法