Java设计模式之单例模式及在Android中的重要使用

时间:2021-11-23 20:49:58

  之前在开发中老用到一些设计模式可是呢又不是很懂,于是狠下心来琢磨一番。下面是我琢磨后总结的,希望对您有用。如果发现了问题,请帮忙指正。

一、单例模式是什么?

  单例模式最初的定义出现于《设计模式》:“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
  Java中单例模式定义;“一个类有且仅有一个实例,并且自行实例化向整个系统提供该实例。”

二、为什么用单例模式?

  对于系统中的某些类来说,只有一个实例很重要。例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只有有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID生成器。如在Windows OS 中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着某一瞬间系统有多个状态,与实际不符,也会为用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例是非常重要的。
  如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。

三、单例模式特点

单例模式特点有三个
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给其他对象(整个系统)提供这一实例。
 从具体实现角度分析,一是单例模式的类只提供私有的(private)构造函数,二是类定义中含有一个该类的静态私有(private static)对象,三是该类提供了一个静态的(static)公有的(public)函数用于创建或获取它本身的静态私有对象。

四、Java中几种常见单例模式写法

  通过上面的介绍你是不是对单例模式有了一个总的概念?没有,那接下来继续给你们放大招。
  基于单例模式特点,单例对象通常作为程序中存放配置信息的载体(想想Android中的Application经常在里面做一些配置的初始化),因为它能够保证其他对象读取到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。

1、饿汉式单例

//饿汉式单例类.在类初始化时,已经自行实例化 
public class Singleton {
//私有的默认构造子
private Singleton() {}
//已经自行实例化
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}

  上面例子中,在这个类被加载时,静态变量single会被初始化,此时类的私有构造子会被调用。这时单例类的唯一实例就被构造出来了。
  饿汉式其实是一种比较形象的称谓。既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例—>

private static final Singleton single = new Singleton();

  饿汉式是典型的空间换时间,当类装载时就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。

2、懒汉式单例

//懒汉式单例类.在第一次调用的时候实例化自己 
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}

  上面的懒汉式单例类实现里对静态工厂方法使用了同步化,以处理多线程环境。
  懒汉式其实是一种比较形象的称谓。既然懒,那么在创建对象实例的时候就不着急。会一直等到马上要使用对象实例 的时候才会被创建,懒人嘛,总是推脱不开的时候才会真正执行工作,因此在装载对象的时候不创建对象实例。

private static Singleton single=null;  

  懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间
  由于懒汉式的实现是线程安全的,这样会降低整个访问的速度,而且每次都要判断。那么有没有更好的方式实现呢?

3、双重检查加锁

  可以使用“双重检查加锁”的方式来实现,就可以达到实现线程安全,又能使性能不受很大影响。
  双重检查加锁:并不是每次进入getInstance()都需要同步,而是先不同步,进入方法后,先检查单例对象是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例(单例对象),这是第二重检查。这样就只需要同步一次,从而减轻了多次在同步情况下进行判断所浪费的时间。
  “双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。不清楚volatile的看过来volatile解析
代码实例:

public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized (Singleton.class) {
//再次检查实例是否存在,如果不存在才真正的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}

  这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
  提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
  根据上面的分析,常见的两种单例实现方式都存在小小的缺陷,那么有没有一种方案,既能实现延迟加载,又能实现线程安全呢?那就是下面一种方法,放大招了,接着呦。

4、静态内部类

public class Singleton {

private Singleton(){}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
*/

private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/

private static Singleton instance = new Singleton();
}

public static Singleton getInstance(){
return SingletonHolder.instance;
}
}

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

5、单例和枚举

public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例。
*/


uniqueInstance;

/**
* 单例可以有自己的操作
*/

public void singletonOperation(){
//功能处理
}
}

按照《高效Java 第二版》中的说法:单元素的枚举类型已经成为实现Singleton的最佳方法。用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可。

  对我来说,我比较喜欢第一种和第四种方式,简单易懂。而且在JVM层实现了线程安全(如果不是多个类加载器环境)。一般的情况下,我会使用第一种方式,只有在要明确实现lazy loading效果时才会使用第四种方式

五、Android中典型的单例模式Application类

1、Application是什么?

  Application和Activity,Service一样,是android框架的一个系统组件,当android程序启动时系统会创建一个 application对象,用来存储系统的一些信息。通常我们是不需要指定一个Application的,这时系统会自动帮我们创建,如果需要创建自己 的Application,也很简单创建一个类继承 Application并在manifest的application标签中进行注册(只需要给Application标签增加个name属性把自己的 Application的名字定入即可)。
  android系统会为每个程序运行时创建一个Application类的对象且仅创建一个,所以Application可以说是单例 (singleton)模式的一个类.且application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局 的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象。所以通过Application来进行一些,数据传递,数据共享 等,数据缓存等操作。

2、巧妙运用单例模式特点,通过Application来传递数据

  假如有一个Activity A, 跳转到 Activity B ,并需要推荐一些数据,通常的作法是Intent.putExtra() 让Intent携带,或者有一个Bundle把信息加入Bundle让Intent推荐Bundle对象,实现传递。但这样作有一个问题在 于,Intent和Bundle所能携带的数据类型都是一些基本的数据类型,如果想实现复杂的数据传递就比较麻烦了,通常需要实现 Serializable或者Parcellable接口。这其实是Android的一种IPC数据传递的方法。如果我们的两个Activity在同一个 进程当中为什么还要这么麻烦呢,只要把需要传递的对象的引用传递过去就可以了。
基本思路是这样的。在Application中创建一个HashMap ,以字符串为索引,Object为value这样我们的HashMap就可以存储任何类型的对象了。在Activity A中把需要传递的对象放入这个HashMap,然后通过Intent或者其它途经再把这索引的字符串传递给Activity B ,Activity B 就可以根据这个字符串在HashMap中取出这个对象了。只要再向下转个型 ,就实现了对象的传递。

六、总结

经过网上的爬文终于了解了什么是单例模式,在这里感谢
http://www.cnblogs.com/java-my-life/archive/2012/03/31/2425631.html
http://blog.csdn.net/songylwq/article/details/6058771
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
http://www.cnblogs.com/hxsyl/archive/2013/03/19/2969489.html
http://blog.csdn.net/pi9nc/article/details/11200969