单例模式
一个类有且仅有一个实例,并且自行实例化向整个系统提供
基本用法
五种用法:懒汉,饿汉,双重校验锁,枚举和静态内部类。
饿汉式
加载时实例化
public class SingletonA {
private static SingletonA instance = new SingletonA();
private SingletonA() {
}
public static SingletonA getInstance() {
return instance;
}
}
懒汉式
使用时实例化
public class SingletonB {
private static SingletonB instance;
private SingletonB() {
}
public static synchronized SingletonB getInstance() {
if (instance == null) {
instance = new SingletonB();
}
return instance;
}
}
双重校验锁
被volatile
修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
由于volatile关键字可能会屏蔽掉虚拟机中的一些必要的代码优化,所以运行效率并不是很高。
public class SingletonC {
private volatile static SingletonC instance;
private SingletonC() {
}
public static SingletonC getInstance() {
// 先检查实例是否存在,如果不存在才进入下面的同步块
if (instance == null) {
// 同步块,线程安全的创建实例
synchronized (SingletonC.class) {
// 再次检查实例是否存在,如果不存在才真正的创建实例
if (instance == null) {
instance = new SingletonC();
}
}
}
return instance;
}
}
静态内部类
public class SingletonD {
private static class InstanceHolder {
public static final SingletonD INSTANCE = new SingletonD();
}
private SingletonD() {
}
public static final SingletonD getInstance() {
return InstanceHolder.INSTANCE;
}
}
枚举
不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。1.5中才加入enum特性
public enum SingletonE {
INSTANCE;
public void otherMethod() {
Log.d("Singleton", "E");
}
}
特别用法
集合管理
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
private SingletonManager() {
}
public static void putInstance(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getInstance(String key) {
if (objMap.containsKey(key)) {
return objMap.get(key);
}
return null;
}
}
问题
类加载器
如果单例由不同的类加载器载入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类加载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
解决方法:
private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null){
classLoader = Singleton.class.getClassLoader();
}
return classLoader.loadClass(classname);
}
序列化
如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和反序列化。不管怎样,如果你序列化一个单例类的对象,接下来反序列化多个那个对象,那你就会有多个单例类的实例。
解决方法:
public class SingletonF implements java.io.Serializable{
private static SingletonF instance = new SingletonF();
private SingletonF() {
}
public static SingletonF getInstance() {
return instance;
}
// 添加此方法确保序列化单例
private Object readResolve() throws java.io.ObjectStreamException {
return instance;
}
}