date: 2017-04-10 23:21:08
单例模式的定义
单例模式是以个比较简单的模式,其定义如下:
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
优点:
单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。可以在系统设置全局的访问点,优化和共享资源访问。
缺点:
单例模式一般没有接口,拓展困难,除了修改代码基本没有第二种途径可以实现;对测试不利,单例模式没有完成,是不能进行测试的;单例模式与单一职责原则有冲突。
理解懒汉与饿汉:
懒汉和饿汉的本质区别,就是实例化对象的时机。
“懒汉式”是在你真正用到的时候才去建这个单例对象。
“饿汉式”类加载的时候,就把单例的初始化完成了。
如何写好单例模式?
不好的写法:
第一种:懒汉式。多线程情况下,多个线程可以同时通过
If(singleton == null) 创建多个Singleton(),所以是线程不安全的。
public Class Singleton{
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance ;
}
}
第二种 懒汉,线程安全。在getInstance()加上synchronized,实现同步,此时线程安全,但是在方法加上synchronized后,在一个线程执行getInstance()的,其他线程都被阻塞了,所以性能比较低下。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
下面我们看比较高效+线程安全的写法。
第一种: 双重检查锁定(double-checked locking) ,这种写法在第一次初始化之后,其他线程再来getInstance的时候会省掉很多因为同步带来的的性能消耗。
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
但是,这个版本的写法在指令重排的情况下会出错。
可以在 private static Singleton instance 加入 volatile 改为
private static volatile Singleton instance
volatile:这个关键字有两层语义。第一层语义相信大家都比较熟悉,就是可见性。可见性指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。顺便一提,工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。volatile的第二层语义是禁止指令重排序优化。
这样子应该可以了吧?
然而,禁止指令重排优化这条语义在jdk1.5以后才能正确工作
关于这个写法存在的问题,涉及到的知识比较深入了,现在暂时不做深究,感兴趣的朋友可以尝试阅读《深入理解JVM》里面类加载的相关内容。
此外DCL还有其他的写法,比如从synchronized一个类改成一个static 的Object对象。
第二种:饿汉式。缺点是它不是一种懒加载模式,单例会在加载类后一开始就被初始化。
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
第三种:Effective Java 推荐单例用法:静态内部类法。这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷。
public class Singleton {
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}