二十三种设计模式之单例模式(一)

所有的单例模式大致可以归为5类,除了注册式外,其他几类实现方式步骤大致为构造方法私有,提供私有静态变量,提供全局访问点。

 下面会对这五类单例实现具体分析其优缺点

一、饿汉式

类加载的时候就实例化对象

优点:程序的效率比较高

缺点:当有大量类时会占用较大内存,是典型的空间换时间的做法。

代码较为基础省略

二、懒汉式

懒汉式单例正好相反是在使用到该类的时候进行初始化实例

优点:节约了宝贵的内存空间

缺点:效率比较低且存在线程安全问题

思考:1.具体是怎么引起的问题?

  有两种结果

a.结果是正常的

    1.多线程环境下,两个线程按照正常顺序执行,获取的仍是单例对象

    2.多线程环境下,两个线程同时进入if判断语句,A线程在创建完对象准备return 的时候 B线程获取CPU开始执行,也创建了对象且return出去。 导致B线程把结果给覆盖了,打印出来的是同一个实例,其实内部发生了很多^_^

这个是假的单例。

b.破坏了单例

   多线程环境下,两个线程同时进入if判断,且A线程打印完结果,B线程开始执行,也创建了对象。导致实际获取了多个对象,破坏了单例

      2.那么我们如何解决线程安全问题?

 version1

public class LazySingleInstance {
    private LazySingleInstance() {
    }

    private static LazySingleInstance instance;

    public static synchronized LazySingleInstance getInstance() {
        if (instance == null) {
            instance = new LazySingleInstance();
        }
        return instance;
    }
}

这种写法可以解决上述问题,但是加上synchronized之后代码执行效率会降低。

 

version2

public class LazyDoubleCheckedInstance {

    private LazyDoubleCheckedInstance() {
    }

    private static volatile LazyDoubleCheckedInstance instance;

    public static LazyDoubleCheckedInstance getInstance() {
        //第一次检查是否要加锁
        if (instance == null) {
            synchronized (LazyDoubleCheckedInstance.class) {
                //第二次检查该对象是否存在
                if (instance == null) {
                    instance = new LazyDoubleCheckedInstance();
                }
            }
        }
        return instance;
    }
}

可以在version1的基础上进行优化,当对象不为空就直接返回结果,不需要加锁。 

第二次判断是多线程环境下可能导致破坏单例的情况。这样使用了双重检查锁之后 就可以完美解决线程安全问题。眼见的同学可能已经看到静态变量instance加了一个volatile关键字,这是因为多线程环境下

new LazyDoubleCheckedInstance()在JVM底层会被拆分成3个指令

1.memory = allocate(); //1:分配对象的内存空间

2.ctorInstance(memory); //2:初始化对象

3.instance = memory; //3:设置instance指向刚分配的内存地址  

这3种指令没有互相依赖关系 所以为了提高效率,这三个指令是会进行指令重排序的。

重排序后可能是下面的结果

1.memory = allocate(); //1:分配对象的内存空间

3.instance = memory; //3:设置instance指向刚分配的内存地址  

2.ctorInstance(memory); //2:初始化对象

这样的话对象还没初始化就被分配到了内存地址,线程B虽然进不去synchronized关键字,但是第一个if判断结果不为空,就会返回一个未初始化完成的对象出去,可能会导致问题。所以加上了volatile关键字保证了内存的可见性。

但是这样做代码不够优雅,尤其是里面的两个if语句对于新手不够友好,可读性差。下面看下升级版的

version3

静态内部类单例

public class SingleInnerClassInstance {
    private SingleInnerClassInstance() {
    }


    public static SingleInnerClassInstance getInstance() {
        return InnerSingleInstance.instance;
    }

    private static class InnerSingleInstance {
        private static SingleInnerClassInstance instance = new SingleInnerClassInstance();
    }
}

我们可以利用java语法优势,这种写法即不会占用内存,因为类加载的时候我们只会加载

SingleInnerClassInstance.class 文件 只有当我们使用内部类的时候才会加载SingleInnerClassInstance$InnerSingleInstance.class文件

并且不会有线程安全问题,是一种比较优雅的写法。

但是这种写法也有个问题,就是能被反射破坏。我们该如何解决呢?

这也就引出了注册时单例,由于篇幅原因,后面几种单例会在下一篇文章更新,到时候我们一起来探讨~ 下期见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值