关闭

你真的了解Singeton吗?

标签: java设计模式singleton
239人阅读 评论(0) 收藏 举报
分类:

时代在进步,随着人们对编程语言理解程度的不断加深,设计模式也在不断进步。笔者对Singleton模式的理解,也经历了不断加深的一个过程。今天我们就来聊聊Java语言最常用的设计模式之一——单例模式(Singleton)。

1. 什么是Singleton

Singleton的正式提出,自然来自GoF的《设计模式》。单例模式中的“单例”通常用来代表那些本质上具有唯一性的系统组件(或者叫做资源)。比如文件系统、资源管理器等等。

GoF:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

对于Java语言,需要考虑的问题就更复杂一点,主要是Singleton定义的范围。不说明范围的Singleton就是在耍流氓

1、对于Java,一般单实例的范围应该时JVM内唯一。

2、每个类加载器(ClassLoader)都能够加载同一个类,使得在同一JVM中存在多个实例。所以在多加载器情形,应当首先考虑单实例的应用范围。

对于Singleton的各种书写方式及其分析,有一篇文章比较全面:http://www.cnblogs.com/520yang/articles/4535096.html

2. 为什么使用Singleton

本段中,如无特殊说明,默认Singleton范围是本JVM,只有一个ClassLoader。

Singleton有一个“强力竞争者”:静态变量+静态变量的类。很多情况下它们能够实现同样的功能,所以我们必须弄明白Singleton独有的优势。

我们先看一下Singleton的基本形式:

public class SingletonBasic {
    // The Singleton factory part
    private static SingletonBasic instance;
    public static SingletonBasic getInstance(){
        if (instance == null){
            instance = new SingletonBasic();
        }
        return instance;
    }
    // The Singleton provided functions
    private Object var1;
    private Object var2;
    private SingletonBasic(){}

    public void doSomething(){System.out.print(var1);}
    public int returnSomething(){
        return 0;
    }
}

通过观察,我们可以从这个基本形式,总结一下Singleton的特点。这种写法虽然在工程代码中不建议,但是作为麻雀解剖却是极好的。

  1. 自己负责自身实例的创建和维护。
  2. 通过实例对外提供功能。
  3. 有延迟加载的效果。
  4. 扩展为N实例模式时,外部不感知。

简单说,Singleton就是工厂+单态。相对于如下的静态方式,单例模式的特点,似乎大部分都有问题了。

public class SingleStatic {
    private static Object var1 = new Object();
    private static Object var2 = new Object();

    private SingleStatic(){
        throw new Exception();
    }

    public static void doSomething(){
        System.out.print(var1);
    }
    public static int returnSomething(){
        return 0;
    }
}

对于上面的写法,

第一,根本就不需要创建实例,而且,不允许外部调用构造函数。这方面比单例模式更加安全(当然单例模式还有更安全的枚举写法)。

第二,所有功能均具备。差别仅仅在于延迟加载,以及可能扩展为N实例的需求。

对于N实例的扩展需求,这本质上是工厂模式的优势所在,算不得单例模式的好处。那么,使用Java单例模式的好处,就只剩下了延迟加载这一个。然而延迟加载是否真的那么重要?以至于要用单独的一个模式来支持它?

面对这个明显的不合理,经过“慎重思考”,我终于又想到一个理由:不需要一直驻留内存,但最多只能有一个实例的情况。

然而,不需要一直驻留内存,则需要使用完后就把内存回收,但是我们的Singleton经典写法,实例却是static的,这意味着只要实例化一次,就永远无法垃圾回收!这是怎么话说的?笔者瞬间凌乱了……

3. 进一步分析

经过上述分析,我们看到,Singleton的应用场景,只有三个:

  1. 需要延迟加载的特性。比如,要加快系统启动速度,用Singleton将服务包装,避免大量数据在类初始化时就加载进来。

  2. 不需要一直常驻内存。这时使用Singleton,由于其实例是static的,所以必须通过显式的方法,在不再需要的时候,将实例注销。

  3. 可能会扩展为对象池。

4. 使用Singleton的正确姿势

A. 多线程

通过上述分析,我们发现,Singleton天生就要应对多线程的场景。所以,线程安全肯定是必须考虑的因素。我们在网上一搜,多数介绍Singleton相关的文章都是把多线程放在首位,而且对线程安全的写法的分析也是最早出现的。现在我们应该明白为什么会如此了。

在所有线程安全的写法中,静态内部类和枚举是被人们所推荐的。尤其是枚举,更是得到了一些大牛的支持。

B. 测试

然而,Singleton模式还有另外一个扰不过去的问题,那就是测试困难。这个难题如何解决呢?

其实单例模式对外提供的是一组服务。之所以测试困难,是因为这些服务依赖于内部的状态,而内部的状态值在初始化时又依赖于其它不方便模拟的资源。下边的代码是一种解决思路:

public enum XXService{
    service;
    private int value;
    XXService(){
        DataProvider provider = Factory.getProvider();
        value = provider.provide();
    }
    public int getValue(){return value;}
}
interface DataProvider{
    int provide();
}
// 注意这里是包权限
class Factory {
    private static instance = new DefautProvider();
    static DataProvider getProvider(){
        return instance;
    }
    static void set(DataProvider userDefine){
        instance = userDefine;
    }
}

C. 需要注销的情况

在这种情况下,单实例往往就不能把工厂与自身结合到一起了。于是工厂独立出来后,形成一个Registry。单实例在初始化时,把自己注册到Registry中;一旦不再需要,便可以注销。

如果硬要使用经典模式,也不是不可以,只是需要增加一个delete函数来消除静态实例。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:21459次
    • 积分:527
    • 等级:
    • 排名:千里之外
    • 原创:32篇
    • 转载:2篇
    • 译文:0篇
    • 评论:4条
    文章分类
    最新评论