所谓单例模式(Singleton),就是保证一个类仅有一个实例,并提供一个访问它的全局访问点的模式。
玩过反恐精英的同学应该都知道,游戏当中是有个控制台的,我们可以通过按`键(波浪线键)调出这个控制台。控制台的目的是为了方便开发人员调试,当然我们也可以在里面来修改一些游戏参数,如输入SV_GRAVITY 100可以把重力调整到100,那么我们跳跃的高度就是原来的8倍了。
由于控制台的游戏的全局通用的,因此我们希望这个控制台类仅有一个实例。当我们访问它的时候,如果它没有实例化,则实例化之,如果它实例化了我们则返回它实例化的对象。这便是单例模式。
那么,以Java为例,我们应该在何时将类的对象实例化呢?是在第一次加载类的时候?第一次需要返回实例的时候?因为对象的实例化时间顺序的差异,我们可以写出几种单例模式的实现方法,本篇文字以懒汉、饿汉、嵌套类(内部静态类)三种方法为例。代码如下:
class Log {
public void print(String str){
System.out.println(str + " - From 懒汉" );
}
private static Log logInstance;
private Log(){}
public static synchronized Log getInstance(){
if (logInstance == null){
logInstance = new Log();
}
return logInstance;
}
}
class Log2{
private static Log2 logInstance = new Log2();
private Log2(){}
public void print(String str){
System.out.println(str + " - From 饿汉" );
}
public static Log2 getInstance(){
return logInstance;
}
}
class Log3{
private static class LogHolder{
private static final Log3 logInstance = new Log3();
}
private Log3(){}
public static Log3 getInstance(){
return LogHolder.logInstance;
}
public void print(String str){
System.out.println(str + " - From 内部静态类" );
}
}
class Singleton
{
public static void main(String[] args) {
Log log = Log.getInstance();
log.print("Hello world");
Log2 log2 = Log2.getInstance();
log2.print("Hello world");
Log3 log3 = Log3.getInstance();
log3.print("Hello world");
}
}
在上面的例子中,Log、Log2和Log3分别用懒汉、饿汉、内部静态类实现了单例模式,它们能够调用print方法,输出我们传入的字符串。下面简要来分析一下这3个类之间的差异。
Log类可以调用getInstance来返回一个Log实例,如果这个实例没有被创建,则进行创建,否则直接返回实例,这个就是懒汉模式(需要的时候才进行判断)。由于判断是否存在和创建存在时间差,因此我加上了synchronized关键字保证它不会在多线程中遭到争抢。这样写的代价是,系统会在同步锁上有很大的开销,假设很多地方需要使用到getInstance,那么时间开销会很大。
Log2类,将实例化写在了一个static语句中,那么,只要这个类被加载(这个类第一次被使用)时,就会创建logInstance对象。假设这个对象十分庞大,那么我们在加载这个类的时候会花很多时间。另外,假设new Log2()执行失败,我们将永远无法得到Log2的实例。只要这个类被加载,则对象被创建,这个就是饿汉。
Log3类包含一个嵌套类(内部静态类),它是Log2类的升级版。我们可以看到,logInstance的实例化被写到了一个嵌套类中,那么这个嵌套类被加载的时间也就是调用Log3.getInstance的时间,也就是说,只有我们调用了Log3.getInstance,这个对象才会被创建。
以上程序的运行结果为:
Hello world - From 懒汉
Hello world - From 饿汉
Hello world - From 内部静态类
当一个类只期望有一个实例,且客户可以从一个众所周知的地方访问它时,就可以考虑单例模式了。