一、为什么要用单例模式

   当一个类需要用来管理共享资源的时候,我们就只需要一个对象,比方说:线程池、缓存、日志对象等等。如果说制造出了多个实例,就会导致许多问题的产生,例如:程序异常,资源使用过量,结果不一致等等。

   举个简单的例子就是,当一个web应用中连接数据库的Connection对象,如果每次访问都new一个出来,那么当有一万个甚至更多的访问在短时间内并发,这将导致服务器资源的大量开销,因为这些对象不回被垃圾回收立刻收回。但如果服务器对此只实例化一次,每次Connection对象被使用后放回线程池,其他访问过来时在重新使用它,这便使得服务器性能大大的提升了,这里就要用单例模式去实现程序了。


二、金典单例模式实现

public class Singleton {
    private static Singleton sl;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if(sl == null) {
            sl = new Singleton();
        }
        return sl;
    }
    //其他的方法
}

*注意:金典单例实现只适用于单线程的情况,当有多个线程并发时,它们都要执行这段代码,都执行到第6行,这时sl都为空,于是乎就会实例出多个对象了,这就违背了单例模式的思想了。


三、多线程场景下的单例模式

1.大家很容易想到在getInstance()方法上加上synchronized关键字就能解决线程并发时的问题了。

public class Singleton {
    private static Singleton sl;
    private Singleton() {
    }
    public static synchronized Singleton getInstance() {
        if(sl == null) {
            sl = new Singleton();
        }
        return sl;
    }
    //其他的方法
}

*注意:但是我们可以想象得到,真正需要同步的是在第一次调用时,之后就不再需要同步这个方法了。之后的每次调用,同步都会是一种累赘。程序执行的效率就会大大降低。


2.使用“急切”创建实例,而不用延迟延迟实例化的做法

public class Singleton {
    private static Singleton sl = new Singleton();//保证了线程安全
    private Singleton() {
    }
    public static synchronized Singleton getInstance() {
        return sl;
    }
    //其他的方法
}

JVM虚拟机在加载这个类时就会立马创建此唯一的单例。


3.使用“双重加锁”,在getInstance()方法中减少使用同步

public class Singleton {
    private volatile static Singleton sl;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if(sl == null) {
            synchronized (Singleton.class) {
                if(sl == null) {
                    sl = new Singleton();
                }
            }
        }
        return sl;
    }
    //其他的方法
}

这样就能延迟创建实例,并且是线程安全的。


四、总结

   为了保证能在多线程场景下程序不会出错,通常我们应该选择后两种实现方式中的一种。至于具体用哪种更好,这个可以更具具体的需求来定:

   1.程序总是创建并使用单例时,选择“急切”创建实例的实现吧。

   2.如果不是1,就用“双重加锁”吧。

   最后,在单线程的情况下,还是推荐使用金典。金典感觉总是挺好的。嘿嘿。