设计模式--单例模式
单例模式是一种最常见的设计模式。
为什么
所谓单例模式,就是一个类仅有一个实例存在。为什么有这种要求呢,因为一个类有多个对象的话,可能会消耗过多的资源,以Android中常用的图片加载框架ImageLoader为例,ImageLoader中包含线程池,缓存系统,网络请求等,很消耗资源,因此没有理由让它构造多个实例,我们在使用的时候,也是通过ImageLoader.getInstance()获得ImageLoader的实例。
定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。

要实现单例模式,主要有以下几个关键点:
- 构造方法不对外开放,为private
Java中创建对象主要是通过new来创建,但是单例模式不允许外接通过new的方法来创建对象。 - 通过一个静态方法返回单例对象
- 要保证对象只有一个,尤其是在多线程环境下
所有单例模式的UML图如下所示:

UML图比较简单,只有一个实例,提供一个获取实例的方法。
单例模式的写法比较多,关键要知道每种写法的优劣,选择适合自己的写法即可:
写法一,饿汉模式(静态常量,可用)
public class Singleton(){
private static Singleton instance=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
...
}
优缺点
可以看到这个实例,
优点:在类装载的时候就完成初始化,避免了线程同步问题。
缺点:在类加载的时候创建,即使用不到,也会创建,在一定程度上造成了资源的浪费。
写法二,懒汉模式(线程不安全)
public class Singleton {
private static Singleton instance=null; //利用一个静态变量来记录Singleton的唯一实例
private Singleton() { //构造方法声明为私有,拒绝外接通过new的方式创建实例
}
public static Singleton getInstance() { //提供一个static方法,提供唯一的实例
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
缺点:懒汉形式存在的问题是,在多线程下不能保证实例只有一个。懒汉模式有个判空操作,
if (instance == null) {
instance = new Singleton();
}
初始时instance是空的,如果有多个线程同时运行至此,有可能会创建多个实例,instance就不唯一了。所以对于高并发来创建instance的操作,懒汉模式并不实用。
理解懒汉和饿汉
懒汉式,饿汉式这是一种比较形象的称呼。
饿汉式,既然饿,在创建对象的时候比较着急,在装载类的时候就创建对象。
懒汉式,既然懒,在创建实例对象的时候就不着急,会一直等到马上要使用对象实例的时候创建。
补充
懒汉模式和饿汉模式,在创建实例的时候,都有个static关键字。
懒汉模式:
private static Singleton instance=null;
饿汉模式:
private static Singleton instance=new Singleton();
那么这两个static到底哪个用到了static的特性呢。Java中static的特性描述如下:
- static变量在类加载的时候初始化。
- 多个变量的static变量会公共享同一块内存区域
这样看来,只有饿汉模式用到了static的特性,那么懒汉模式呢,因为懒汉模式中instance用于static修饰的方法中,所以必须要加static。
写法三,懒汉模式(线程安全)(不推荐用)
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
可以看到,每次在执行getInstance()方法时,都要进行同步一下。
优点:解决了懒汉模式线程不安全的问题。
缺点:效率变低,每次getInstance()都要同步一下,其实只要初始的时候同步一下就可以,后面要想获取实例的话,只要return就可以了,每次都同步会造成效率变低。
写法四,双重检查(推荐用)
private static volatile Singleton instance;
public static Singleton getInstance(){
if (instance==null){
synchronized (Singleton.class){
if (instance==null){
instance=new Singleton();
}
}
}
return instance;
}
可以看到,有两次检查instnace是否为空的判断,第一次判断是为了解决效率问题,只有当instance为空时,才会同步代码。第二次判空是为了防止出现多个实例。其中volatile关键字是为了防止指令重排。
写法五,静态内部类(推荐用)
private static class SingletonInstance{
private static final Singleton INSTANCE=new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
由于SingletonInstance是一个静态内部类,它加载的时机是当它被调用的时候,它在外部类的getInstance()方法被调用,所以只有在getInstance()方法被调用时,才会去加载,达到了延迟加载的目的。
对于内部类SingletonInstance,它采用了类似于饿汉式的单例模式实现,保证了实例的单一。
写法六,枚举
public enum SingleInstance {
INSTANCE;
public void doSomething(){
// do something
}
}
使用
SingleInstance.INSTANCE.fun1();
最简单的单例模式,既保证了线程安全,也没有同步问题,不过在时机项目中,很少见到在实际的项目中运用。