单例模式是一种比较简单的模式,其定义如下:
Ensure a class has only one instance, and provide a global point of access to it(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例)
主要优点:
1、提供了对唯一实例的受控访问。
2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
3、允许可变数目的实例。
主要缺点:
1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
实现方式
1.饿汉式单例类(类加载是就初始化)-----线程安全
public class Singleton {
//私有的类成员常量
private static final Singleton SINGLETON=new Singleton();
//私有的默认构造方法,此类不能被继承
private Singleton(){}
//静态工厂方法
public static SingletongetInstance(){
return SINGLETON;
}
}
饿汉变种:
public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}
2.懒汉模式
线程不安全:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
线程安全:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
线程安全并且考虑效率(双重校验锁 JDK1.5之后)
public class Singleton {
private volatile static Singleton singleton = null;
private Singleton (){}
public static Singleton getSingleton() {
//检查实例是否存在,不存在进入同步块
if (singleton == null) {
synchronized (Singleton.class) {
//在此检查实例是否存在,不存在才真正创建
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检查加锁: “双重检查加锁“的方式可以既实现线程安全,又能够使性能不受到很大的影响。
那么什么是”双重检查加锁“机制呢?
所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。
进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。
这样一来,就只需要同步一次了,从而减少了多次在 同步情况下进行判断所浪费的时间。
双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
说明:由于volatile关键字可能会屏蔽掉虚拟机中的一些必要的代码优化,所以运行效率并不是
很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用”双重检查加锁“
机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
此处请参考FinderCheng 的 Blog
3.枚举实现单例模式(JDK1.5之后)
public enum SingletonWithEnum {
INSTANCE;
public static SingletonWithEnum getInstance() {
return INSTANCE;
}
}
Effective Java作者Josh Bloch 提倡这种方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,但很少有人这么用,或许是应为enum在jdk1.5之后才出来,大家都已经用习惯用常规的设计模式实现了。
4.静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。
参考 cantellow Blog
问题( 来自 cantellow Blog)
有两个问题需要注意:
1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题修复的办法是:
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null) {
classLoader = Singleton.class.getClassLoader();
}
return (classLoader.loadClass(classname));
}
对第二个问题修复的办法是:
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}