1、定义
单例模式属于创建型模式,确保一个类只有一个实例,并提供一个全局的访问接口。
2、使用场景
在开发工程中,有些对象我们只需要一个:线程池、缓存、硬件设备等, 创建多个实例则会造成结果的不一致和资源的过多消耗。
3、UML类图
4、经典的实现方式
1、懒汉模式
/**
* 懒汉模式
*/
public class SingleTon01 {
private static SingleTon01 instance;
private SingleTon01() {
}
public static synchronized SingleTon01 getInstance() {
if (instance == null) {
instance = new SingleTon01();
}
return instance;
}
}
懒汉模式在使用的时候才进行实例化,在一定程度上节约了资源,但是在每次调用getInstance()都进行了同步,造成不必要的同步。
2、Double Check Lock(DCL)模式
/**
* Double Check Lock(DCL)双锁检测
*/
public class SingleTon02 {
private volatile static SingleTon02 instance;
/**
* 构造方法私有化,不允许外部创建对象
*/
private SingleTon02() {
}
public static SingleTon02 getInstance() {
if (instance == null) {//第一次判空避免不必要的同步
synchronized (SingleTon02.class) {
if (instance == null) {//第二次判空为了在null的情况下创建实例
instance = new SingleTon02();
}
}
}
return instance;
}
}
instance = new Singleton()不是一个原子操作,执行过程分为三步:
- 给Singleton的实例分配内存;
- 调用Singleton的构造函数,初始化成员字段;
- 将instance对象指向分配的内存空间(此时instance将不再是null)。
由于Java编译器存在指令重排序,也就是说上面的执行顺序无法得到保证,如果在执行完第三步时还没有来得及执行第二步就切换到其它线程,这是其它线程读取到的instance将会为null,DCL就会失效,当然在高并发的环境下发生的概率很小。
可以使用volatile来避免这个问题, 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
-
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
-
禁止进行指令重排序。
3、静态类部类模式
/**
* 静态类部类模式
*/
public class SingleTon03 {
private SingleTon03() {
}
private static class SingletonHolder {
private static final SingleTon03 instance = new SingleTon03();
}
public static SingleTon03 getInstance() {
return SingletonHolder.instance;
}
}
这种方式不仅能够保证线程安全,同时也能够保证单例对象的唯一性,延迟单例对象的实例化。
4、枚举实现单例
/**
* 枚举单例
*/
public enum SingleTon04 {
INSTANCE;
}
枚举对象的创建是现成安全的,并且也是唯一的。
5、利用容器实现单例
/**
* 利用容器实现单例
*/
public class SingleTon05 {
private static Map<String, Object> objMap = new HashMap<>();
private SingleTon05() {}
public void addObj(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getObj(String key) {
return objMap.get(key);
}
}
我们可以通过这种方式管理多种类型的管理,如对activity的管理。
5、总结
优点:1、全局只有一个实例,减小了系统性能开销。
缺点:1、扩展困难,只能通过修改代码的方式进行扩展。
2、如果单例持有Context对象,容易造成内存泄漏问题,一般传递Application对象。
代码:https://gitee.com/os2chen/DesignPattern
参考:《Head First Design》、《Android源码设计模式解析与实战》