2、使用最广泛的模式-单例模式

来源:互联网 发布:java 计算圆周率 编辑:程序博客网 时间:2024/06/01 19:02

1. 概述

单例模式是最简单的一种设计模式,先来看看它的定义:

Ensure a class has only one instance, and provide a global point access of it
确保一个类只有实例,并且自动实例化改实例,并且提供一个全局的接口能够获取这个唯一的实例。

  • 减少内存的开支,特别是一个对象需要频繁创建和销货时。并且当一个类在内存中只存在一个实例时减少系统内存的开销。
  • 单例模式可以避免对资源的多重占用,比如写一个文件,可以避免对同一个文件进行写操作。

2. 简单示例

单例模式虽然简单,但是也有许多的坑,我们从最简单的单例模式说起:

//饿汉模式class Singleton{    private final Singleton instance = new Singleton();    //将默认的构造函数私有化,防止通过其他的方式new出改类的实例    private Singleton(){      }    public static Singleton getInstance(){         return instatnce;    }}
  • 饿汉模式:避免了多线程的同步问题,节省了运行时的判断(运行时间)。但是无论调不调用getInstance()都会在加载类的时候初始化实例,浪费了内存。
//懒汉模式class Singleton{    private final Singleton instance;    //将默认的构造函数私有化,防止通过其他的方式new出改类的实例    private Singleton(){      }    public synchronized static Singleton getInstance(){         if(instance == null)            instance = new Singleton();         return instatnce;    }}
  • 懒汉模式:达到了懒加载,只有在使用的时候才会初始化,并且第一次加载的时间稍长。并且为了避免多线程操作出现同步问题,加入了synchronized字段,这就造成了每次获取实例的时候都会在线程同步上浪费时间,造成不必要的开销。这种模式一般不建议采用。

3.单例模式的多种实现

3.1 Double Check Lock(DCL) 实现单例

DCL方式实现单例模式的有点在于既能够在需要时进行初始化,又能保证线程安全,且单例对象在初始化后调用getInstance不进行同步锁,代码如下:

public class Singleton {    private volatile static Singleton sInstance = null;    private Singleton(){    }    public static Singleton getsInstance(){        //为了避免不必要的同步,因为只有在未初始化时才需要进行同步        if(sInstance == null){            synchronized (Singleton.class){                if(sInstance == null){                    sInstance = new Singleton();                }            }        }        return sInstance;    }}

为什么要判断两次sInstance == null

因为sInstance = new Singleton();这条语句不是原子的。这条语句会被最终汇编为以下几条指令:
1)改sInstance分配内存
2)初始化sInance字段
3)将sInstance对象指向分配的内存(此时sInstance才不为null)

在JDK1.5及以前上面2.3的执行顺序是不能保证的,因此可能在1-3-2时,对象还未处理好就被另一个线程给取走了,使用时就会出现问题,此时采用DLC检查就失效了。

在JDK1.5以后,java调整了volatile关键字,与C语言中的volatile定义相同,表明变量可能在使用时被意想不到的改变,此时优化器在用到这个值时会小心的读取这个变量的值(即逐一的运行会变代码)。因此在jdk1.6及以后只要在变量前加入volatile关键字就能保证dlc是有效的。

虽然volatile关键字会影响一部分性能,但能换取来程序的稳定,牺牲这点性能还是值得的。

3.2 静态内部类单例模式

public class Singleton {    private Singleton() {    }    public static final Singleton getInstance() {        return SingletonHolder.INSTANCE;    }    /**     * 静态内部类     */    private static class SingletonHolder {        private static final Singleton INSTANCE = new Singleton();    }} 
  • 使用内部类。只有被外部类调用时才会创建实例,同时不用加锁避免了线程同步问题,结合了饿汉、懒汉、DLC的优点,是最佳的单例模式的实现方式

3.3 枚举单例

枚举在java中与普通类是一样的,不仅能够有字段,还能有自己的方法,最重要的是默认是线程安全的,并且在任何情况下都是一个单例。

最终要的是上述几种模式,在一个情况下会出现重复创建对象的情况,就是反序列化。反序列化提供了一个很特别的HOOK函数,readResolve();如果要杜绝反序列化的可以在类中添加该方法。

private Object readResolve() throws ObjectStreamException{    return sInstance;}

使用枚举单例:

public enum Singleton{      //定义的枚举常量,也就是静态类实例      INSTANCE;    //下面添加提供的各种成员及静态方法}

3.4 使用容器实现单例模式

在程序的开始讲多种单例类型注入到统一的管理类中,在使用时根据key获取对应的类型的对象,这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的即可进行获取操作,降低了用户的使用成本,也对用户隐藏了具体的实现,降低了耦合度

public class SingletonManager {   private static Map<String, Object> objMap = new HashMap<String,Object>();  private Singleton() { }  public static void registerService(String key, Objectinstance) {    if (!objMap.containsKey(key) ) {      objMap.put(key, instance) ;    }  }  public static ObjectgetService(String key) {    return objMap.get(key) ;  }}

4. android系统源码中所应用的单例模式

在android系统中源码中使用的单例模式,多是采用容器实现单例模式,在静态代码块中完成注册。如LayoutInflater、getSystemService等。

最后:在android中,静态实例持有contex时容易造成内存泄漏,此时注意传递给静态实例的contex最好是application的contex。

0 0
原创粉丝点击