单例模式

Posted wendy777

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。

背景:单例模式模式是在编程中经常使用,他可以对需要使用的资金进行一次性初始化,防止多次初始化多次资源释放带来性能的开销。

最近在读《JAVA并发编程的艺术》发现有些知识点不错,整理出来。

单例模式常用模式是懒汉模式和饿汉模式

懒汉模式:就是用到时候才new出来。

饿汉模式:类一开始就加载好,可直接使用。

 

单线程情况下,我们会通过以下实现才生成一个懒汉模式的单例模式类。但是多线程访问时候,这种实现不满足要求。

public class Singleton {
    public static Singleton instance = null;

    public Singleton() {
        //todo
    }

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

上面实现,如果在多线程情况下,可能出现多个线程同时访问instance == null 出现问题。接下来,介绍几种可以避免多线程竞争访问导致的问题。

(一)同步处理,延迟初始化,任何时刻只有一个线程抢占到访问资源。

public class Singleton {
    public static Singleton instance = null;

    public Singleton() {
        //todo
    }

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

但是这个方法也是弊端,由于采用同步处理,synchronized导致性能开销。如果getInstance()被多个线程频繁调用,会导致程序执行性能下降。反之,如果getInstance()不被多个线程频繁调用,那么这个延迟初始化将是不错的选择。

(二)双重检查锁,优化方案一出现多线程频繁调用instance(instance!=null)时阻塞的问题。

public class Singleton {
    public static Singleton instance = null;

    public Singleton() {
        //todo
    }

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

优点:如果instance不为null, 多线程访问instance就不会出现阻塞,提高了性能,减少了开销。

缺点:由于JAVA内存模型,不同JIT编译器之间出现重排序问题,可能会出现线程A执行instance时instance==null,于是进入instance = new Singleton(), 线程B发现instance != null,但是这个instance可能未初始化。接下来我来细化下JMM执行过程方便理解。

instance = new Singleton() 可以分解成以下几步:

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

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

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

由于在为伪代码中2和3之间可能会出现重排序. 引入《JAVA并发编程的艺术》的图。懒得自己画了。

技术分享图片

鉴于这个问题,我们可以有如下更佳解决方式,把instance定义为volatile。JDK5之后引入volatile关键字,解决各个编译器处理器缓存和总线内存之间数据一致性问题。当把对象声明为volatile后,2和3重排序问题的问题,在多进程中被禁止,即不允许重排序。

public class Singleton {
    public static volatile Singleton instance = null;

    public Singleton() {
        //todo
    }

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

(三)如果你使用Spring来管理bean, @Autowired注释本身就是单例模式,一开始就生成好一个bean实例,即为饿汉模式。

@Component
public class Singleton {
    
    @Autowired
    public Singleton(....){
        //TODO
    }
}

 

以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

性能比较好的单例写法

片段作为 Android 中的单例

单例片段或保存网页视图状态

你熟悉的设计模式都有哪些?写出单例模式的实现代码

单例模式以及静态代码块