单例模式定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的使用场景
确保某个类只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种对象有且只能有一个。
单例模式的UML图
角色介绍:
- Client --------------- 高层客户端
- Singleton ----------- 单例类
实现单例模式的要点:
- 构造函数不对外开放,一般是 Private;
- 通过一个静态方法或者枚举类返回单例类对象;
- 确保单例类的对象有且只有一个,尤其是在多线程环境下;
- 确保单例类对象在反序列化时不会重新构建对象;
饿汉单例模式
一个公司只有一个CEO,可以有几个VP,无数个员工。
/*
* 普通员工
* */
public class Staff {public void work(){System.out.println("干活");}
}
/*
* 副总裁
* */
public class VP extends Staff{public void work(){System.out.println("管理下面的经理");}
}
/*
* CEO 饿汉单例模式
* */
public class CEO extends Staff{private static final CEO mCeo = new CEO();// 构造函数私有private CEO(){}// 公有的静态函数,对外暴露获取单例对象的接口public static CEO getmCeo(){return mCeo;}public void work(){System.out.println("管理VP");}
}
/*
* 公司类
* */
public class Company {private List<Staff> staffList = new ArrayList<Staff>();public void addStaff(Staff per){staffList.add(per);}public void showAllStaffs(){for (Staff per : staffList){System.out.println("obj : " + per.toString());}}
}
测试类
public class Test {public static void main(String[] args) {Company company = new Company();// CEO 只能通过getCeo函数获取CEO ceo = CEO.getmCeo();CEO ceo1 = CEO.getmCeo();company.addStaff(ceo);company.addStaff(ceo1);// 通过 new 创建vp 对象VP vp = new VP();VP vp1 = new VP();// 通过 new 创建 staff 对象Staff staff = new Staff();Staff staff1 = new Staff();Staff staff2 = new Staff();company.addStaff(vp);company.addStaff(vp1);company.addStaff(staff);company.addStaff(staff1);company.addStaff(staff2);company.showAllStaffs();}
}
运行结果如下图,我们可以发现 CEO 的地址是一样的,说明构建的对象实例是唯一的
懒汉单例模式
public class Singleton {private static Singleton instance;private Singleton(){}public static synchronized Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}
}
上面的代码中增加了一个 synchronized 关键字,也就是 getInstance 是一个同步方法,这是保证在多线程下单例对象唯一性的手段。
懒汉单例模式的优点是单例只有在使用时才会被实例化,在一定程度上节约了资源;
缺点是第一次加载需要及时进行实例化,反应稍慢,最大问题是每次 getInstance 都进行同步,造成不必要的同步开销。
Double Check Lock (DCL) 单例模式
public class Singleton {private static Singleton instance = null;private Singleton(){}public void doSomething(){System.out.println("do.sth.");}public static Singleton getInstance(){if(instance == null){synchronized (Singleton.class){if (instance == null){instance = new Singleton();}}}return instance;}
静态内部类单例模式
/*
* 静态内部类
* */public class Singleton {private Singleton(){}public static Singleton getInstance(){return SingletonHolder.sInstance;}/** 静态内部类* */private static class SingletonHolder{private static final Singleton sInstance = new Singleton();}
}
当第一次加载 Singleton 类时并不会初始化 sInstance ,只有在第一次调用 Singleton 的 getInstance 的方法才会导致 sInstance 被初始化。所以,第一次调用 getInstance 方法会导致虚拟机加载 SingletonHolder 类,这种方式不仅能够确保线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化。
枚举单例
public enum SingletonEnum {INSTANCE;public void doSomething(){System.out.println("do.sth.");}
}
枚举单例最大的优点就是简单,枚举在 java 中与普通的类是一样的,不仅能有字段,还能有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下都是一个单例。
使用容器实现单例模式
public class SingletonManger {private static Map<String,Object> objectMap = new HashMap<String,Object>();private SingletonManger(){}public static void registerService(String key,Object instance){if (!objectMap.containsKey(key)){objectMap.put(key,instance);}}public static Object getService(String key){return objectMap.get(key);}
}
在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式可以让我们管理多种类型的单例,并且在使用是通过统一的接口进行获取操作,降低用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。