1.单例模式介绍
单例模式是应用最广的模式之一。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多系统只需要拥有一个全局的对象,遮掩更有利于我们协调整体的统一行为。2.单例模式的定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。3.单例模式的使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对只应该有且只有一个。例如,创建 一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时就要考虑使用单例模式4.单例模式的分类
饿汉模式和懒汉模式5.单例模式的UML类图
6.实现单例模式的关键点
1.构造函数对外不开放,一般为private2.通过一个静态方法或者枚举返回单例对象
3.确保单例类的对象有且只有一个,尤其是在多线程情况下
4.确保单例对象在反序列化时不会被重新构建
7.单例模式的几种写法
7.1 饿汉模式
饿汉模式是类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的 // 普通人群
public class Person {
String name;
int age;
public Person() {
}
public void work() {
}
}
// 教师
public class Teacher extends Person {
public void work() {
}
}
// 校长
public class Principal extends Person {
private static final Principal mPrincipal = new Principal();
// 构造函数私有
private Principal() {
}
// 公有的静态函数,对外暴露获取单例对象的接口
public static Principal getInstance() {
return mPrincipal;
}
}
public class SingletonDemo {
public static void main(String[] args) {
// 教师对象通过New获取
Teacher tea_0 = new Teacher();
Teacher tea_1 = new Teacher();
// 校长对象通过getInstance方法获取
Principal pri_0 = Principal.getInstance();
Principal pri_1 = Principal.getInstance();
// 普通人群对象通过New获取
Person per_0 = new Person();
Person per_1 = new Person();
System.out.printf("tea_0: " + tea_0.toString() + "\n");
System.out.printf("tea_1: " + tea_1.toString() + "\n");
System.out.printf("pri_0: " + pri_0.toString() + "\n");
System.out.printf("pri_1: " + pri_1.toString() + "\n");
System.out.printf("per_0: " + per_0.toString() + "\n");
System.out.printf("per_1: " + per_1.toString() + "\n");
}
}
7.2 懒汉模式
懒汉模式是声明一个静态对象,并且用户在第一次调用getInstance时进行初始化。优点:单例只有在使用时才会被实例化,在一定程度上节省了资源
缺点:第一次加载时需要及时进行实例化,反应稍慢,最大的问题在于每次调用getInstance都进行同步,造成不必要的同步开销
Public class Principal extends Person {
/** 懒汉模式 */
private static Principal mPrincipal = null;
// 构造函数私有
private Principal() {
}
public static Principal getInstance() {
if (null == mPrincipal) {
mPrincipal = new Principal();
}
return mPrincipal;
}
}
7.3 DCL(Double Check Lock)实现单例
在getInstance方法中对mPrincipal进行两次判断,第一次判断主要是为了避免不必要的同步,第二次判断主要是为了在null的情况下创建实例并保证线程安全优点:既能够在需要时才进行实例初始化,同时保证了线程安全,且单例对象实例化后,调用getInstance不进行同步锁
缺点:第一次加载时反应速度慢,由于JVM内存模型,偶尔会出现实例化失败
public class Principal extends Person {
/** DCL模式 */
private static Principal mPrincipal = null;
// 构造函数私有
private Principal() {
}
public static Principal getInstance() {
if (null == mPrincipal) {
synchronized (Principal.class) {
if (null == mPrincipal) {
mPrincipal = new Principal();
}
}
}
return mPrincipal;
}
}
7.4 静态内部类单例模式
当第一次加载Principal时并不会初始化mPrincipal,只有在第一次调用Principal的getInstance才会实例化mPrincipal。因此在第一次调用getIntance方法时加载PrincipalHolder,这种方式不仅能够保证线程安全也能够保证实例化对象的唯一性。 public class Principal extends Person {
/** 静态内部类实现单例模式 */
// 构造函数私有
private Principal() {
}
public static Principal getInstance() {
return PrincipalHolder.mPrincipal;
}
private static class PrincipalHolder {
private static Principal mPrincipal = new Principal();
}
}
7.5枚举单例
默认枚举创建单例是线程安全的,并且在任何情况下,它都是一个单例。 public enum Principal {
INSTANCE;
private Principal(){
}
public void work(){
}
}
使用
Principal.INSTANCE.work();
7.6 登记模式
在程序初始化时,将多个单例类型注入到一个统一的管理类中,在使用时,根据key获取对应类型的对象,这种方式除了方便我们管理多种类型的单例,并且在使用时通过统一的接口进行获取操作,实现了对用户的隐藏。
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<>();
private SingletonManager() {
}
public static void registerSingleton(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getSingleton(String key) {
return objMap.get(key);
}
}</span>
参考资料:
1.Android源码设计模式时间