单例模式是应用最广的设计模式之一,面试的时候常常会被要求写个单例模式的Demo,那么我今天就来看看单例模式的相关内容。
本文的主要内容如下:
- 定义
- 使用场景
- 实现方式
- 懒汉式
- 双重检验锁
- 饿汉式
- 静态内部类
- 枚举
- Android源码中的单例模式
- 总结
定义
确保某一个类有且只有一个实例,并且自行实例化并向整个系统提供这个实例。
使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多资源,或者某种类型的对象只应该有且只有一个。(例如创建一个对象需要消耗过多的资源,如访问IO和数据库等资源时)
实现方式
实现一个单例模式,主要有如下几个关键点:
- 构造函数一般不对外开放,一般为private;
- 通过一个静态方法或者枚举返回单例对象;
- 确保单例模式对象有且只有一个,尤其是在多线程下;
- 确保单例类对象不会在反序列化时重新构建对象。
单例模式的实现方式有多种,具体如下:
1. 懒汉式:
感觉命名都是比较形象的称谓。懒人嘛,总是推脱不开的时候才会真正去执行工作,那么在创建对象实例的时候就不着急创建实例,会一直等到马上要使用对象实例的时候才会创建。
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方法式都会进行同步,那么就保证了线程安全。但是,有个明显的缺陷,即使instance已经被初始化,getInstance方法还是会进行同步,一样就会消耗不必要的资源。
2. 双重检验锁(Double Check Lock)
DCL方式算是懒汉式的改进,既能够在需要时才初始化,由能保证线程安全。
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
DCL在同步锁的基础上,添加1层 if判断:若单例已创建,则不需再执行加锁操作就可获取实例,从而提高性能。
3. 饿汉式
既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
饿汉式就比较简洁了,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
缺点也很明显,单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。因此饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
4. 静态内部类
DCL尽管在一定程度上解决了资源消耗过多、多余的同步等问题,但是在某些情况下仍然有失效的可能。因此出现了静态内部类。
public class Singleton {
private static class SingletonHolder{
private final static Singleton instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
这种写法仍然使用JVM本身机制保证了线程安全问题。由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类SingletonHolder,在该内部类中定义了一个static类型的变量instance ,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。同时由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
5. 枚举
用枚举写单例实在太简单了,总共就三行代码!这也是它最大的优点。
public enum Singleton {
INSTANCE;
}
我们可以通过Singleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
Android源码中的单例模式
使用容器实现单例模式
在Android系统中,存在很多系统级别的服务,如WindowsManagerService、ActivityManagerService等,在程序的初始会以单例的形式统一的注册在系统的管理类中,在使用时根据key获取对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户使用成本,也对用户隐藏了具体实现,降低了耦合度。
总结
单例模式在一般情况下直接使用饿汉式就好了,如果明确要求要懒加载则倾向于使用静态内部类。如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。
网友评论