设计模式之禅读书笔记-单例模式你真的掌握了吗?

date: 2017-04-10 23:21:08

单例模式的定义

单例模式是以个比较简单的模式,其定义如下:
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)

优点:
单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。可以在系统设置全局的访问点,优化和共享资源访问。

缺点:
单例模式一般没有接口,拓展困难,除了修改代码基本没有第二种途径可以实现;对测试不利,单例模式没有完成,是不能进行测试的;单例模式与单一职责原则有冲突。

理解懒汉与饿汉:
懒汉和饿汉的本质区别,就是实例化对象的时机。

“懒汉式”是在你真正用到的时候才去建这个单例对象。
“饿汉式”类加载的时候,就把单例的初始化完成了。

如何写好单例模式?

不好的写法:

第一种:懒汉式。多线程情况下,多个线程可以同时通过
If(singleton == null) 创建多个Singleton(),所以是线程不安全的。

public Class Singleton{

    private static Singleton instance;

    private Singleton (){} 
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        
        return instance ;
}

}

第二种 懒汉,线程安全。在getInstance()加上synchronized,实现同步,此时线程安全,但是在方法加上synchronized后,在一个线程执行getInstance()的,其他线程都被阻塞了,所以性能比较低下。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
    }  
            return instance;  
    }  
}

下面我们看比较高效+线程安全的写法。

第一种: 双重检查锁定(double-checked locking) ,这种写法在第一次初始化之后,其他线程再来getInstance的时候会省掉很多因为同步带来的的性能消耗。

public class Singleton{
    private static Singleton instance;
        private Singleton(){}
        public static Singleton getInstance(){
            if(instance == null){
                synchronized(Singleton.class){
                     if(instance == null){
                        instance = new Singleton();
                      }
                }
            }
            return instance;
    }
}

但是,这个版本的写法在指令重排的情况下会出错
可以在 private static Singleton instance 加入 volatile 改为
private static volatile Singleton instance

volatile:这个关键字有两层语义。第一层语义相信大家都比较熟悉,就是可见性。可见性指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。顺便一提,工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。volatile的第二层语义是禁止指令重排序优化。

这样子应该可以了吧?

然而,禁止指令重排优化这条语义在jdk1.5以后才能正确工作
关于这个写法存在的问题,涉及到的知识比较深入了,现在暂时不做深究,感兴趣的朋友可以尝试阅读《深入理解JVM》里面类加载的相关内容。
此外DCL还有其他的写法,比如从synchronized一个类改成一个static 的Object对象。

第二种:饿汉式。缺点是它不是一种懒加载模式,单例会在加载类后一开始就被初始化。

public class Singleton{
//类加载时就初始化
    private static final Singleton instance = new Singleton();

    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}

第三种:Effective Java 推荐单例用法:静态内部类法。这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷。

public class Singleton {
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}
    原文作者:增其Mrlu
    原文地址: https://www.jianshu.com/p/551a2da50c36
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞