定义:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例
使用场景:
确保某个类只有一个对象,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有一个。例如创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时就要考虑使用单例模式。 《Effective Java》中提到要不要使用单例取决于,这个类的所有实例在功能上都是相互等价的。
关键点:
1,构造函数不对外开放,一般为private
2,通过一个静态方法或者枚举返回单例类对象
3,确保单例类的对象有且只有一个,尤其是在多线程环境下
4,确保单例类对象在反序列化时不会重新构建对象
实现方式:
饿汉模式:
public class Singleton {
private static final Singleton sInstance = new Singleton();
private Singleton() {
public static Singleton getInstance() {
return sInstance;
}
}
优缺点:静态对象声明时就已经初始化。加载类比较慢,但是运行时获取对象的速度比较快,而且是线程安全的;
懒汉模式
public class Singleton {
private static Singleton sInstance;
private Singleton() {
public static synchronized Singleton getInstance() {
if(sInstance== null)
sInstance= new Singleton();
return sInstance;
}
}
优缺点:优点是对象只有在使用时才会被实例化,在一定程度上节约了资源。缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步造成不必要的同步开销。这种模式一般不建议使用。
双检锁(Double Check Lock)模式
public class Singleton {
private volatile static Singleton sInstance;
private Singleton() {
}
public static Singleton getInstance() {
// 避免不必要的同步
if (sInstance == null) {
synchronized (Singleton.class) {
// sInstance为null的情况下创建实例
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
这种方式偶尔会有DCL失效的问题,假设线程A执行到 sInstance = new Singleton() 语句,这里看起来是一句代码,但实际上并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了3件事情:
- 给Singleton的实例分配内存
- 调用Singleton的构造方法,初始化成员字段
- 将sInstance对象指向分配的内存空间(此时sInstance就不是null了)
由于Java编译器允许处理器乱序执行,以及JDK1.5之前的JMM(Java Memory Model,Java内存模型)中的Cache、寄存器到主内存回写顺序的规定,上面2和3的顺序是无法保证的。执行的顺序可能是1-2-3也能是1-3-2。如果是后者,并且在3执行之后,2执行之前,被切换到B线程上,这时候sInstance因为已经在线程A内执行了3,sInstance已经不为空了,所以线程B直接取走sInstance使用就会出错,这就是DCL失效问题。
JDK1.5之后,sun官方重新定义了volatile关键字,如果是JDK1.5或之后的版本,上面的代码可以保证原子性和可见性。就可以放心使用这种方式来实现单例。
优缺点:第一次执行getInstance()时对象才会被实例化,效率高。但是第一次加载时反应稍慢,JDK1.5之前可能会失败。除非在并发场景比较复杂并且JDK1.5之前使用,否则一般能够满足需求。
静态内部类模式
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
优缺点:加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance()时才会加载SingletonHolder类,初始化sInstance对象。不仅能够保证线程安全,也能够保证单例对象的唯一,同时也延迟了对象的实例化,是推荐的实现方式。
枚举单例
public enum Singleton {
INSTANCE;
}
之前几种模式,通过序列化写入磁盘,再反序列化读回来,就可以获取到一个新的实例。反序列化操作提供了一个很特别的钩子函数readResolve(),这个方法可以让开发人员控制对象的反序列化。上面几种方式如果要避免在被反序列化时重新生成对象,那么需要加入以下方法:
private Object readResolve() throws ObjectStreamException {
return sInstance;
}
序列化意味着一个对象的生存周期并不取决于程序是否正在执行。对于Serializable,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个Externalizable对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用readExternal()。《忘记是哪本书了》 Externalizable 继承自 java.io.Serializable。通常我们不需要实现Serializable接口,所以一般也不会有序列化的问题。虽然所有的枚举类都实现了Serializable接口,通过反序列化返回的依然是同一个对象。
以上几种方式使用反射同样可以拿到一个新的对象,枚举同样可以避开这个问题,在获取构造方法的时候就会抛出java.lang.NoSuchMethodException的异常,保证实例的唯一性。
优缺点:写法简单,即使反序列化也不会生成新的实例。
使用容器实现单例模式
// 注册服务
registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
// 加入到map中
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
// 获取服务
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
// CachedServiceFetcher实现了ServiceFetcher接口
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
if (service == null) {
try {
service = createService(ctx);
cache[mCacheIndex] = service;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return (T)service;
}
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
android中各种系统服务的实例都是使用这种方式获取的实例对象,注册服务的时候以name为key,以一个CachedServiceFetcher对象为value存入到map中,在获取服务的时候,通过name拿到CachedServiceFetcher对象,调用getService()方法,fetcher.getService(ctx)最后会调用CachedServiceFetcher的createService方法创建实例然后缓存起来,下次直接返回。
优缺点:这种方式可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。