定义
确保某一个类只有一个实例,并且自行实例化向整个系统提供该实例
场景
整个系统只需要一个全局的实例对象。在衡量对象资源占用时,比如IO、数据库访问等,不希望出现多个实例化对象的时候
关键点
- 构造函数私有化
- 通过一个静态方法或者枚举返回单例类对象
- 确保单例类对象有且只有一个,特别是在多线程的环境下
- 确保单例对象在反序列化时不会重新构建对象
以上四点中,第四点不是必须的,只在涉及反序列化的系统中有要求,下文设计的说明中,该关键点不参与优劣的评判。
创建方式
饿汉模式
在类的初始化阶段就创建单例类对象
public class Singleton {
private static Singleton sInstance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return sInstance;
}
}
优点:轻松的解决了多线程问题。(类的初始化操作由虚拟机确保是同步的)
缺点:一开始就创建了类对象,造成内存空间的浪费
说明:这种方式一般不建议使用
懒汉模式
在首次获取单例类对象时创建单例类对象
public class Singleton {
private static Singleton sInstance;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
return sInstance;
}
}
优点:需要时才创建对象,节省内存开销
缺点:每次获取单例类对象时,都会对代码进行同步锁处理,消耗不必要的系统资源
说明:这种方式同样不建议使用
DCL模式
双重校验锁模式。类似懒汉模式,在首次获取时创建单例对象,针对懒汉模式的优化
public class Singleton {
private static Singleton sInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
优点:需要时才创建对象,节省内存开销,外层非加锁判空操作,避免同步锁的额外开销
缺点:外层判空在某些情况下,不可靠,针对多线程可能存在风险
说明:需要借助
volatile
关键字来协助解决外层不可靠的问题。在JDK1.6(不包含)以下时,volatile
关键字不能妥善的处理变量的可见性。在JDK1.6及以上版本,volatile
关键字已经解决了该问题。
这种方式也是目前使用比较多的一种。
静态内部类
通过在单例类中创建一个私有的静态容器类,该容器类中定义静态常量并初始化,类型为单例类的类型。在每次获取单例类对象时,直接返回内静态内部类的静态成员变量即可。
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
}
优点:很好的解决了多线程问题。不会在类初始化阶段创建对象。
缺点:需要创建额外的静态内部类。
说明:这种方式优势比较明显,而且没有明显的劣势。也是部分IDE在自动化生成单例类是采用的方式。推荐使用这种方式。
通过枚举类实现
借助枚举实例的创建是线程安全的,并且只会实例化一次的特性来设计的。而且由于枚举类的特殊性,保证了反序列化时不会生成新的实例对象
public enum SingletonEnum {
INSTANCE;
public int sum(int x, int y) {
return x + y;
}
}
优点:多线程安全、保证真实的单例(枚举特性),书写简单
缺点:枚举类区别于普通的类,在接口设计和交互层有明显的障碍
说明:这种方式优势非常明显,劣势也非常明显。基于谷歌的说法,凡是使用枚举的地方,都可以通过静态常量来解决。
通过容器来实现
通过容器类来实现属于另辟蹊径。借助一些稳定的工具类来实现,比如说Map,Set等,可以看下面代码块
public class Singleton {
private static Map<String, Object> container = new HashMap();
private Singleton() {
}
public static Object getInstance(String key) {
return container.get(key);
}
public static void addInstance(String key, Object obj) {
if (!container.containsKey(key)) {
container.put(key, obj);
}
}
}
优点:可以实现多个类的单例模式
缺点:借助额外的工具类,使用场景比较局限
说明:通过管理类容器,可以实现多个类的单例对象获取。这种方式在特定的环境下会起到很好的作用。
涉及到的问题
根据单例类的特性,一旦单例对象被创建,因为该实例是静态的,到GC-ROOT具有应用周期内的可达性。因此在单例模式下,需要谨慎的处理好单例对象的引用关系,防止内存泄漏
总结
综合考量对资源的开销、实现复杂度等因素,采用静态内部类的形式来设计单例类是目前被推崇的方式