开发者模式之—-单例模式
—前序:本文为《Androdi源码及开发模式》的学习笔记。
定义:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用情景:
确保某个类有且只有一个对象的情景,避免每次使用都重复创建对象,而消耗过多资源。例如,创建一个对象需要消耗的资源过多,如要访问IO或数据库资源,这是就要考虑单例模式。
详细介绍:
通过将单例类的构造函数私有化,使得客户端代码不能通过new的形式手动构造单例类的对象。单例类会暴露一个公有的静态方法,客户端需要调用这个这个静态方法获取到单例类的唯一对象吗,在获取到这个单例对象的过程中需要确保线程安全,也就是在多线程的环境下构造单例类的对象也是有且只有一个,这也是单例模式中最困难的地方。
关键点:
- 构造函数不对外开放,一般为private;
- 通过一个静态方法或者枚举返回单例类对象;
- 确保单例类的对象有且只有一个,尤其是在多线程的环境下;
- 确保单例类对象在反序列化时不会重新构造对象。
UML图:
单例模式的写法一般有以下几种:
第一种、最普通的单例模式(饿汉模式):
在类加载时就进行初始化,不管方法有没有被调用。
public class SingleTon1 {
private static final SingleTon1 instance = new SingleTon1();
private SingleTon1() {
}
public SingleTon1 getInstance() {
return instance;
}
}
第二种:懒汉模式:
利用synchronize 进行同步,在getInstance第一次调用时进行初始化,在多线程下利用synchronize保证单例对象的唯一性,但是这样每次执行都会执行进行同步,消耗不必要的资源
优点:单例只有使用时才会被实例化,在一定程度上节约了资源
缺点:第一次加载时需要及时地进行实例化,反应相当稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的开销。
*这种模式不建议使用!
public class SingleTon2 {
private static SingleTon2 instance;
private SingleTon2() {
}
public static synchronized SingleTon2 getInstance() {
if (instance == null)
instance = new SingleTon2();
return instance;
}
}
第三种:Double Check Lock(DCL)双重判空实现单例
对instance进行了两次判空,第一次判空是为了避免不必要的同步,相当Single2的懒汉模式的每次同步优化了很多, 第二次判空是为了在null的情况下创建实例,
* 优点:第一次执行getInstance时单例对象才被实例化,效率高
* 缺点:第一次加载时反应稍慢
public class SingleTon3 {
private static SingleTon3 singleTon3;
public SingleTon3() {
}
public static SingleTon3 getInstance() {
if (singleTon3 == null) {
synchronized (SingleTon3.class) {
if (singleTon3 == null)
singleTon3 = new SingleTon3();
}
}
return singleTon3;
}
}
第四种:静态内部类单例模式
第一次加载SingleTon类时不会初始化singleTon4,只有调用getInstance方法时才被初始化。这种方法不仅确保线程安全,也能够保证单例对象的唯一性,
* 同时也延迟了单例的实例化,这是最推荐使用的单例模式实现方式
public class SingleTon4 {
public SingleTon4() {
}
public static SingleTon4 getInstance() {
return SingleTonHolder.singleTon4;
}
private static class SingleTonHolder {
public static SingleTon4 singleTon4 = new SingleTon4();
}
}
第五种:枚举实现单例
默认枚举实例的创建是安全的,并且在任何情况下他都是一个单例
public enum SingleTon5 {
INSTANCE;
public void doSomething() {
System.out.print("do sth.");
}
}
第六种:使用容器实现单例模式
在程序的初始,将多种单例类型注入到统一的管理中。这种方式使得我们可以管理多种类型的单例,在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度
public class SingleTonManager6 {
private static HashMap<String, Object> objMap = new HashMap<>();
public SingleTonManager6() {
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key))
objMap.put(key, instance);
}
public static Object getService(String key) {
return objMap.get(key);
}
}
单例模式的优点:
1、由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建销毁时,而且对象的创建和销毁又无法优化,单例模式的优势就会十分明显。
2、避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对用一个资源文件的同时写操作。
3、单例模式可以在系统设置全局的访问点,优化和共享资源范围。
缺点:
1、单例模式一般没有接口,所以很难扩展,除了修改代码基本没有第二种途径可以实现;
2、单例对象如果持有context,那么很容易造成内存泄漏。此时需要注意传递给单例对象的Context最好是Application Context;