单例模式的五种写法:
懒汉
恶汉
静态内部类
枚举
双重校验锁
1.懒汉
class LazySingleton{
private static LazySingleton singleton;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(singleton==null){
singleton=new LazySingleton();
}
return singleton;
}
}
常用的写法。问题:没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例
2.饿汉
class HungrySingleton{
private static HungrySingleton singleton=new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return singleton;
}
}
在类初始化时,已经自行实例化,缺点:没有达到懒加载的效果
3.静态内部类
class InternalSingleton{
private static class SingletonHolder{
private final static InternalSingleton INSTANCE=new InternalSingleton();
}
private InternalSingleton(){}
public static InternalSingleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
优点:加载时不会初始化静态变量INSTANCE,因为没有主动使用,达到Lazy loading。既实现了线程安全,又避免了同步带来的性能影响。推荐使用
4.枚举
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
《Effective Java》作者推荐使用的方法,优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
虽然Effective Java中推荐使用,但是在Android平台上却是不被推荐的。在这篇Android Training中明确指出:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
5.双重校验锁
class LockSingleton{
private volatile static LockSingleton singleton;
private LockSingleton(){}
//详见:http://www.ibm.com/developerworks/cn/java/j-dcl.html
public static LockSingleton getInstance(){
if(singleton==null){
synchronized(LockSingleton.class){
if(singleton==null){
singleton=new LockSingleton();
}
}
}
return singleton;
}
}
可以看到里面加了volatile关键字来声明单例对象。它有两层语义。
第一层,可见性。指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。
第二层语义是禁止指令重排序优化。由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同。
既然已经起到了多线程下原子性、有序性、可见性的作用,
双重检测锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是失败的一个主要原因。
还有疑问可参考http://www.iteye.com/topic/652440
和http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
好在jdk1.6已经修复了这个问题了
6.Android源代码中的单例模式
以LayoutInflater为例
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
context的具体实现在D:\adt\sdk\sources\android-19\android\app的ContextImpl中
@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
SYSTEM_SERVICE_MAP是啥呢?就是一个HashMap
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP = new HashMap<String, ServiceFetcher>();
接下来追踪fetcher.getService
final ArrayList<Object> mServiceCache = new ArrayList<Object>();
static class ServiceFetcher {
int mContextCacheIndex = -1;
/**
* Main entrypoint; only override if you don't need caching.
*/
public Object getService(ContextImpl ctx) {
ArrayList<Object> cache = ctx.mServiceCache;
Object service;
synchronized (cache) {
if (cache.size() == 0) {
// Initialize the cache vector on first access.
// At this point sNextPerContextServiceCacheIndex
// is the number of potential services that are
// cached per-Context.
for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
cache.add(null);
}
} else {
//从缓存中获取service对象************************************************
service = cache.get(mContextCacheIndex);
if (service != null) {
return service;
}
}
//创建对象********************************************************
service = createService(ctx);
cache.set(mContextCacheIndex, service);
return service;
}
}
/**
* Override this to create a new per-Context instance of the
* service. getService() will handle locking and caching.
*/
public Object createService(ContextImpl ctx) {
throw new RuntimeException("Not implemented");
}
}
总结,创建Service的时候根据key来获得对应的ServiceFetcher,然后通过getService来获取对应的服务。
从缓存中获取,如果没有,就createService再添加到缓存,方便下次获取
这种单例模式是通过缓存和synchronized来完成的
7.优点:
(1)由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
(2)单例模式可以避免对资源的多重占用,例如一个文件操作,由于只有一个实例存在内存中,避免对同一资源文件的同时操作。
(3)单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。
8.缺点:
(1)单例模式一般没有接口,扩展很困难,若要扩展,只能修改代码来实现。
(2)单例对象如果持有Context,那么很容易引发内存泄露。此时需要注意传递给单例对象的Context最好是Application Context。
参考:
http://www.oschina.net/code/snippet_107039_6062
http://blog.csdn.net/jason0539/article/details/23297037
http://www.cnblogs.com/andy-zhou/p/5363585.html#_caption_5