单例模式

单例模式应该是我及大多数Java程序员最早接触的设计模式之一,也是最常用的设计模式之一。

是什么?

单例模式是确保一个类只有一个实例,并提供一个全局访问点的一个模式。

作用及应用场景

在Java应用中,单例模式能保证在一个JVM中,该单例对象只有一个实例存在。

单例模式的uml图:

Singleton.png

应用场景如下:

  1. 在开发中,有一些对象可能我们只需要一个,例如:线程池、缓存、日志对象、一些操作系统设备的类。
  2. 相比于用new关键字创建多个对象,能降低系统开销,减轻jvm压力。

但是,为什么不能用其他东西替代单例(比如,用程序员间的约定替代单例,又比如,Java.lang.Math类是采用静态方法类而非单例的方式实现的?),为什么要用单例?

第一,单就这个事例看,单例模式比约定好,单例模式付出的代价仅仅是代码上的,而且单例却能避免约定中会产生的很多问题。 第二,单例模式与静态类的对比,网上说法众说纷纭,而且有不少是有问题的。我的看法如下。

单例模式和静态类对比

单例模式 静态类
典型例子 java.lang.Runtime java.lang.Math
初始化时间点 用到时创建 加载时创建
OOP(主要) 可以实现接口;单例对象能作为其他类的属性或方法参数 (空)

注:

  • 初始化时间点中仅针对主流用法,单例的时间点也要取决于具体哪种单例,而静态类的加载其实与具体jvm的实现有关,某些jvm是在用到时才创建的。

单例的各种写法

饿汉式加载

public class Singleton1 {
    public static final Singleton1 instance = new Singleton1();

    private Singleton1() {
    }
}

急切的加载写法,对于一些必定会使用到且启动早期就会使用的对象(如日志对象),使用该方法并不是不行。

双重校验锁

/**
 * 双重校验锁
 */
public class Singleton2 {
    private volatile static Singleton2 instance;

    private Singleton2() {
    }

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

能起到延迟加载的效果。注意,由于volitale关键字,java1.5之前的版本不支持该写法。

枚举

public enum Singleton3 {
    INSTANCE;

}

这种书写方式是《Effective Java》中最推荐的单例写法,一目了然,推荐。

静态的内部类

public class Singleton4 {
    private Singleton4(){}

    public static Singleton4 getInstance(){
        return SingletonInstance.instance;
    }

    private static class SingletonInstance{
        static Singleton4 instance = new Singleton4();
    }
}

考虑反射的单例

其实这种情况比较少出现。但是,如果存在享有特权的客户端使用反射,调用私有构造器,就需要防范一下。

枚举天生能免疫这用情况。

双重校验锁能通过在默认构造器中加上如下代码防御反射:

synchronized (Singleton2.class) {
if (instance != null) {
//throw 一个异常
}
}

比较各种单例写法占用的字节数

最近使用java.lang.instrument.Instrumentation,通过javaagent测试了单例的各实现的总大小(Class + Object)。

事实上,测完之后才惊觉这是件“然并卵”的操作,不过测都测了,就在这放出来以供参考吧。(下图忽略英文,4个数字[16,16,24,16]依次是上述4个单例模式占用的字节数) Singleton size tester

References

[1] 知乎|方法区的Class信息,又称为永久代,是否属于Java堆?

results matching ""

    No results matching ""