Java设计模式之从反恐精英控制台分析单例(Singleton)方式

Java设计模式之从反恐精英控制台分析单例(Singleton)模式

  所谓单例模式(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){
            return 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 内部静态类

  当一个类只期望有一个实例,且客户可以从一个众所周知的地方访问它时,就可以考虑单例模式了。