什么是单例模式
单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在一个app里面,有一个类管理用户设置的配置信息:比如是否收集log, 最多收集多少条log,退出是否需要清除app的缓存等。用一个类便于实现全局管理,这些配置信息可能在多个地方用到。
实现单例模式的思路
一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名 称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们 还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
单例模式的实现方法
实现单例模式的方法有很多,但我们要保证调用getInstance()方法只返回同一个实例,同时考虑多线程和效率。以SettingManager为例讲解实现的方法。
1. 饿汉模式
饿汉模式的实现方法如下所示:
public class {
private static final SettingManager INSTANCE = new SettingManager();
private SettingManager() {};
public static SettingManager getInstance() {
return INSTANCE;
}
}
优点:简单方便,不存在线程安全问题。推荐使用。
缺点:不管用到与否,类装载时就完成实例化。不用的时候会造成一定的资源浪费。
2. 双重锁懒汉模式
与饿汉模式相比,懒汉模式是在使用的时候才创建实例,比饿汉模式减少了资源浪费,但复杂度上升,代码如下:
public class SettingManager {
private static volatile SettingManager INSTANCE; //JIT
private SettingManager() {
}
public static SettingManager getInstance() {
if (INSTANCE == null) {
//双重检查, double check
synchronized (SettingManager.class) {
if(INSTANCE == null) {
INSTANCE = new SettingManager();
}
}
}
return INSTANCE;
}
}
优点: 避免资源浪费,使用时才创建
缺点:需要处理多线程情况,通过synchronized解决了线程安全问题,但也会使效率下降,需要双重检查(double check lock)。使用volatile防止指令重排序导致没有初始化的问题。
3. 静态内部类模式
静态内部类把实例的初始化放在静态内部类里面,代码如下:
public class SettingManager {
private SettingManager() {
}
private static class SettingManagerHolder {
private final static SettingManager INSTANCE = new SettingManager();
}
public static SettingManager getInstance() {
return SettingManagerHolder.INSTANCE;
}
}
优点:加载外部类时不会加载内部类,这样可以实现懒加载,只有调用getInstance()方法时才会加载内部类。JVM保证单例,不存在线程安全问题。复杂度比懒汉模式低,外部无法控制实例初始化的过程,无法传递参数进去。
如果单例类需要在脱离单例对象的情况下使用,可以用静态内部类模式。
类只能同时被1个线程初始化,且在同一个加载器中,同一个类不会第二次初始化。所以饿汉模式和静态内部类模式都没有线程安全问题
4. 枚举单例模式
在java中,enum可以像一个正常的class一样定义属性和方法,枚举单例模式是使用枚举类型作为一个单例。代码如下:
public enum SettingManager {
INSTANCE;
private String field1;
private Integer field2;
public void method1(){
// ...
}
public void method2(){
// ...
}
}
优点:不仅可以解决线程同步,还可以防止反序列化。枚举类没有构造方法, 所以不会被反射调用创建新的object。
缺点:可以实现接口,但不能继承。如果将枚举类型作为普通类使用,会失去其作为枚举类型的意义。
如果单例模式可能被破坏,可以使用枚举单例模式。
总结
上面4种方式中,比较推荐饿汉模式,它虽然可能有资源浪费,但这个可能性真的很小,最重要的是它实现起来非常简单。
网友评论