前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单例模式探究

单例模式探究

作者头像
提莫队长
发布2019-02-21 13:42:10
3450
发布2019-02-21 13:42:10
举报
文章被收录于专栏:刘晓杰刘晓杰

单例模式的五种写法: 懒汉 恶汉 静态内部类 枚举 双重校验锁

1.懒汉

代码语言:javascript
复制
class LazySingleton{
    private static LazySingleton singleton;
    private LazySingleton(){
    }
    public static LazySingleton getInstance(){
        if(singleton==null){
            singleton=new LazySingleton();
        }
        return singleton;
    }   
}

常用的写法。问题:没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例

2.饿汉

代码语言:javascript
复制
class HungrySingleton{
    private static HungrySingleton singleton=new HungrySingleton();
    private HungrySingleton(){}
    public static HungrySingleton getInstance(){
        return singleton;
    }
}

在类初始化时,已经自行实例化,缺点:没有达到懒加载的效果

3.静态内部类

代码语言:javascript
复制
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.枚举

代码语言:javascript
复制
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.双重校验锁

代码语言:javascript
复制
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/652440http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html 好在jdk1.6已经修复了这个问题了

6.Android源代码中的单例模式

以LayoutInflater为例

代码语言:javascript
复制
    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中

代码语言:javascript
复制
    @Override
    public Object getSystemService(String name) {
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }

SYSTEM_SERVICE_MAP是啥呢?就是一个HashMap

代码语言:javascript
复制
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP = new HashMap<String, ServiceFetcher>();

接下来追踪fetcher.getService

代码语言:javascript
复制
    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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年02月20日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.懒汉
  • 2.饿汉
  • 3.静态内部类
  • 4.枚举
  • 5.双重校验锁
  • 6.Android源代码中的单例模式
  • 7.优点:
  • 8.缺点:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档