单例模式应该是日常使用最为广泛的一种模式了,也是大家熟悉的模式之一。他的作用是确保某个类只有一个实例,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。比如对数据库的操作时,就可以考虑使用单例模式。
1.单例模式的几种简单使用
实现单例模式几个关键点:
(1)构造函数不对外开放,一般为Private;
(2)通过一个静态方法或者枚举返回单例类对象;
(3)确保单例类的对象有且只有一个,尤其是在多线程环境下;
(4)确保单例类对象在反序列化时不会重新构建对象。
通过将单例类的构造函数私有化,使得客户端代码不能通过new的形式手动构造单例类的对象。单例类会暴露一个公有静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象,在获取这个单例对象的过程中需要确保线程安全,即在多线程环境下构造单例类的对象也是有且只有一个,这也是单例模式实现中比较困难的地方。
(1)饿汉模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
在声明静态对象时就已经初始化,避免了多线程的同步问题。但是不能保证有别的方式去装载,没有达到懒加载。
(2)懒汉模式(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
声明一个静态对象,并且在用户第一次调用getInstance时进行初始化,达到了懒加载,但是在多线程不能正常工作。
(3)懒汉模式(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
getInstance()方法中添加了synchronized关键字,也就是getInstance是一个同步方法,能够在多线程中很好的工作,在多线程情况下保证单例对象唯一性的手段。但是每次调用getInstance方法都会进行同步,反应稍慢,还会造成不必要的开销,所以者这种不建议使用。
(4)DCL单例(双重检查锁定)
public class Singleton {
private static Singleton sInstance = null;
private Singleton() {
}
public void doSomething() {
System.out.println("do sth.");
}
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
既能够在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。
在getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是为了在null的情况下创建实例。
volatile关键字的使用,因为在这里会有DCL失效问题,原因是Java编译器允许处理器乱序执行。那么为了解决这个问题,在JDK1.5之后,具体化了volatile关键字,只要定义时加上他,可以保证执行的顺序,虽然会影响性能。这种方式第一次加载时会稍慢,在高并发环境会有缺陷,但是一般能够满足需求。
能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK 6版本下使用,否则,这种方式一般能够满足需求。
(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,只有在第一次调用Singleton的getInstance方法时才会导致sInstance被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式实现方式。
(6)枚举单例
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("do sth.");
}
}
写法简单是枚举单例最大的优点,枚举在Java中与普通的类是一样的,不仅能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
(7)使用容器实现单例模式
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() { }
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;
}
}
将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
2.Android源码中的单例模式
在Android系统中,我们经常会通过Context获取系统级别的服务,如WindowsManagerService、ActivityManagerService等,更常用的是一个LayoutInflater的类,这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。
举栗子:
我们熟悉的ImageLoader类中将构造函数私有化,并且使用Double Check Lock的形式实现单例,用户通过getInstance方法获取ImageLoader单例对象。在用户使用之前需要使用ImageLoaderConfig来配置ImageLoader,配置合理的情况下才会启动用户指定数量的线程来执行图片加载请求。当用户调用displayImage方法时,ImageLoader会将请求构造成一个BitmapRequest,然后将该请求添加到请求队列中,图片加载线程(RequestDispatcher)会从请求队列(RequestQueue)中获取图片加载请求,然后加载该图片,并且将图片显示到对应的ImageView上,最后将图片缓存到缓存系统中。
了解ImageLoader的更多细节,请下载其源码。
3.总结
单例模式是运用频率很高的模式,但是,由于在客户端通常没有高并发的情况,因此,选择哪种实现方式并不会有太大的影响。即便如此,出于效率考虑,我们推荐使用(4)(5)。
优点:
(1)由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
(2)由于单例模式只生成一个实例,所以,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
(3)单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
(4)单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。
缺点:
(1)单例模式一般没有接口,扩展很困难,若要扩展,只能修改代码来实现。
(2)单例对象如果持有Context,那么很容易引发内存泄露。此时需要注意传递给单例对象的Context最好是Application Context。
参考文章地址:http://www.epubit.com.cn/book/details/4179
介绍完单例模式,接下来讲讲设计模式中的第二种模式—-Builder模式,敬请大家关注!!