专栏首页刘晓杰单例模式探究

单例模式探究

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

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/652440http://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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 美女拼图小游戏

    最上面是自定义的ActionBar,布局里面有两个TextView。一个用于显示level,一个用于显示倒计时 下面就是自定义的RelativeLayout...

    刘晓杰
  • retrofit的使用

    刘晓杰
  • 四大组件的启动过程

    1.Activity启动过程 http://blog.csdn.net/lxj1137800599/article/details/53644259

    刘晓杰
  • 设计模式之——单例模式

    单例模式是在面试中是最容易被考到的设计模式,这是因为单例模式是设计模式中最简单的,几行代码就能搞定(现场手写代码);同时单例模式又有多种实现方式,涉及到线程安全...

    Yano_nankai
  • 内存泄漏的检测、解决、防止

    引言 今天又是没什么事情,好,不多说,直接进入我们的主题吧。 今天说的是关于内存泄漏的检测与解决。这个问题想必对于初学者是个迷,也不知道从何出入手,那么今天这个...

    CSDN技术头条
  • Sublime Text3使用总结

    保存为Sublime Text3\Packages\User\addCurrentTime.py

    小二三不乌
  • 关于spring中的validate注解后台校验的解析

    在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节。比如参数不能为null,email那么必须符合email的格式,如果手动进行if判断或者写正则表达...

    Dream城堡
  • 第5章—构建Spring Web应用程序—关于spring中的validate注解后台校验的解析

    在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节。比如参数不能为null,email那么必须符合email的格式,如果手动进行if判断或者写正则表达...

    Dream城堡
  • 将Azure上的Ubuntu 18.04升级到18.10

    嗯?我竟然又写了一篇很不Windows的文章,别杀我,这还是可以假装和微软Azure有点关系的,谁让微软爱Linux呢。Azure还没有放出Ubuntu 18....

    Edi Wang
  • springboot解决静态属性注入问题

    可以看到,当DSHWechatApiUtil工具类组件进行初始化时,调用@PostConstruct注解标注的方法,对静态变量进行了赋值。

    吟风者

扫码关注云+社区

领取腾讯云代金券