单例模式
定义
一个类有且仅有一个实例,并且自行实例化并向整个系统提供这个实例。
使用场景
确保某个类只有一个对象的场景,避免产生过多对象消耗过多的资源,或者某种类型的对象应该且只有一个。
实现单例模式的关键点
1. 构造函数一般为Private
2. 通过一个静态方法(也可以通过枚举)返回单例对象
3. 确保单例类的对象有且只有一个,尤其是在多线程环境下
4. 确保单例类对象在反序列化时不会重新构建对象
单例的几种写法
- 饿汉式
当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
单例实例在类装载时就构建,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的,所以饿汉式是线程安全的。
public class SingleTon {
private static SingleTon sSingleTon = new SingleTon();
private SingleTon() {
}
public static SingleTon getInstance() {
return sSingleTon;
}
}
-
懒汉式
懒汉模式的优点是只有在单例被使用时候才会实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,并且每次调用getInstance()都会进行同步,务必会造成不必要的开销。
public class SingleTon1 {
private static SingleTon1 sSingleTon;
private SingleTon1() {
}
public static synchronized SingleTon1 getInstance() {
if (sSingleTon == null) {
sSingleTon = new SingleTon1();
}
return sSingleTon;
}
}
- 双重检验锁实现
DCL(Double Check Lock )方式实现单例的优点是既能在需要时候才初始化单例,又能保证线程安全,并且单例对象初始化后调用getInstance()不进行同步锁。
优点:资源利用率高,需要时候才加载,效率高。
缺点:第一次加载反应稍慢,也由于java内存模型原因偶尔会失败,在高并发环境下有一定缺陷(发生概率很小)。
使用volatile关键字,能保证并发下的可见性,如果不使用volatile,由于指令重排序的缘故,可能会导致DCL检验失效。
public class SingleTon2 {
private static volatile SingleTon2 sSingleTon2 = null;
private SingleTon2() {
}
public static SingleTon2 getInstance() {
if (sSingleTon2 == null) {
synchronized (SingleTon2.class) {
if (sSingleTon2 == null) {
sSingleTon2 = new SingleTon2();
}
}
}
return sSingleTon2;
}
}
- 静态内部类实现
这是一种比较推荐的写法,第一次加载SingleTon3类时候并不会初始化sInstance,只有在第一次调用getInstance()方法时会导致虚拟机加载 SingletonHolder类,从而初始化sInstance。
优点: 线程安全,延迟加载
public class SingleTon3 {
private SingleTon3() {
}
public static SingleTon3 getInstance() {
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final SingleTon3 sInstance = new SingleTon3();
}
}
-
容器实现
容器实现的思路与饿汉式有异曲同工之妙,优点是方便管理多个单例对象;缺点也和饿汉式基本一致。
public class SingleTonManager {
private static Map<String, Object> sObjectMap = new HashMap<>();
private SingleTonManager() {
}
public static void registerService(String key, Object o) {
if (!sObjectMap.containsKey(key)) {
sObjectMap.put(key, o);
}
}
public static Object getService(String key) {
return sObjectMap.get(key);
}
}
- 枚举实现
Android中不推荐使用枚举类,因为它占用更多的内存,取而代之是推荐使用@IntDef,@StringDef之类的注解来代替枚举类。
- 枚举类型继承自java.lang.Enum,并自动添加了values和valueOf方法。
- 每个枚举常量是一个静态常量字段,使用内部类实现,该内部类继承了枚举类。
- 所有枚举常量都通过静态代码块来进行初始化,即在类加载期间就初始化。
- enum除了编译时会自动生成private的构造函数外,还会生成一些额外的代码块,而且这些代码块基本都是static的,这样务必会占用更多内存。
public enum SingletonEnum {
INSTANCE;
private String tagName;
public void setTag(String tagName) {
this.tagName = tagName;
}
public String getTag() {
return tagName;
}
@Override public String toString() {
return "SingletonEnum{" + "tagName='" + tagName + '\'' + '}';
}
}
- 测试类
public class Test {
public static void main(String[] args) {
Show show = new Show();
SingleTon singleTon = SingleTon.getInstance();
SingleTon singleTon_ = SingleTon.getInstance();
SingleTon1 singleTon1 = SingleTon1.getInstance();
SingleTon1 singleTon1_ = SingleTon1.getInstance();
SingleTon2 singleTon2 = SingleTon2.getInstance();
SingleTon2 singleTon2_ = SingleTon2.getInstance();
SingleTon3 singleTon3 = SingleTon3.getInstance();
SingleTon3 singleTon3_ = SingleTon3.getInstance();
SingleTonManager.registerService("test", singleTon);
SingleTonManager.registerService("test1", singleTon1);
SingleTonManager.registerService("test2", singleTon2);
SingleTonManager.registerService("test3", singleTon3);
SingleTon singleTonx = (SingleTon) SingleTonManager.getService("test");
SingleTon1 singleTon1x = (SingleTon1) SingleTonManager.getService("test1");
SingleTon2 singleTon2x = (SingleTon2) SingleTonManager.getService("test2");
SingleTon3 singleTon3x = (SingleTon3) SingleTonManager.getService("test3");
SingletonEnum singletonEnum = SingletonEnum.INSTANCE;
singletonEnum.setTag("singletonEnum");
SingletonEnum singletonEnum2 = SingletonEnum.INSTANCE;
UnSingleTon unSingleTon = new UnSingleTon();
UnSingleTon unSingleTon1 = new UnSingleTon();
show.add(singleTon);
show.add(singleTon_);
show.add(singleTonx);
show.add(singleTon1);
show.add(singleTon1_);
show.add(singleTon1x);
show.add(singleTon2);
show.add(singleTon2_);
show.add(singleTon2x);
show.add(singleTon3);
show.add(singleTon3_);
show.add(singleTon3x);
show.add(singletonEnum);
show.add(singletonEnum2);
show.add(unSingleTon);
show.add(unSingleTon1);
show.showAll();
}
private static class Show {
private List<Object> mObjectList = new ArrayList<>();
public void add(Object o) {
mObjectList.add(o);
}
public void showAll() {
for (Object o : mObjectList) {
System.out.println("Obj : " + o.toString());
}
}
}
}
测试结果
image.png常见错误
如果需要在单例中传递一些必要的参数,比如Context对象,这时候需要谨慎点。比如将Activity的context对象通过getInstance()方法传递进去,会不会导致内存泄露?这点是需要考虑到的。
在Android中,如果要传递Context对象,可以考虑使用Application的Context对象。