使用场景
- 只拥有一个全局对象,或者某种类型对象只应该存在一个
- 避免产生多个对象耗费过多的资源
关键点
- 构造函数不对外开放,一般是private
- 通过一个静态方法或者枚举返回单例类对象
- 确保单例类的对象有且只有一个,尤其在多线程环境下
- 确保单例类对象在反序列化时不会重新构建对象
UML
说明:
- Singleton只有一个实例化对象,内部自己实现
- Client通过Singleton的getInstance方法获取实例对象
单例模型实例
公司里面的CEO为例,一个公司可以有几个VP,无数的员工,但是CEO只有一个
基类,普通员工
public class Staff { public void work(){ //干活 } }
副总裁,继承自员工
public class VP extends Staff { @Override public void work() { //管理下面的经理 } }
单例方式的恶汉模式,在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
public class CEO extends Staff { private static final CEO mCeo = new CEO(); //构造函数私有 public CEO() { } //共有的静态函数,对外暴露湖区单例对象的接口 public static CEO getCeo(){ return mCeo; } @Override public void work() { //管理VP } }
公司类
public class Company { private List<Staff> allStaffs = new ArrayList<Staff>(); public void addStaff(Staff per) { allStaffs.add(per); } public void showAllStaffs() { for (Staff per : allStaffs) { System.out.println("Obj : " + per.toString()); } } }
测试函数
public class Main { public static void main(String[] args) { Company company = new Company(); //CEO对象只能通过getCeo函数获取 CEO ceo1 = CEO.getCeo(); CEO ceo2 = CEO.getCeo(); company.addStaff(ceo1); company.addStaff(ceo2); //通过new创建VP对象 VP vp1 = new VP(); VP vp2 = new VP(); company.addStaff(vp1); company.addStaff(vp2); //通过new创建Staff对象 Staff staff1 = new Staff(); Staff staff2 = new Staff(); company.addStaff(staff1); company.addStaff(staff2); //通过打印,判断对象是否统一 company.showAllStaffs(); } }
懒汉模式实现单例,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢
public class CEO1 extends Staff {
private static CEO1 ceo1 = null;
public CEO1() {
}
public static synchronized CEO1 getCeo1() {
if (ceo1 == null) {
ceo1 = new CEO1();
}
return ceo1;
}
@Override
public void work() {
//管理VP
}
}
在一定程度上节约了资源,但在第一次加载时需要及时进行实例化,反应稍慢,最大的问题是在每次调用getCeo时候都进行同步,造成不必要的开支
Double CheckLock(DCL)–双重检查锁定,实现单例
public class CEO2 extends Staff { private volatile static CEO2 mCeo2 = null; public CEO2() {} public static CEO2 getmCeo2(){ if (mCeo2 == null){ synchronized (CEO2.class){ if (mCeo2 == null){ mCeo2 = new CEO2(); } } } return mCeo2; } }
DCL优点是资源利用率高,缺点是由于Java编译器允许处理器乱序执行,以及JDK1.5之前的Java内存模型回写顺序规定,会造成DCL小几率失效问题
静态内部类实现单例模式
public class CEO3 extends Staff{ public CEO3() {} public static CEO3 getCeo3(){ return CEO3Holder.mCeo3; } /** * 静态内部类 */ private static class CEO3Holder{ private static final CEO3 mCeo3 = new CEO3(); } }
静态内部类不仅能够保证线程安全,也能够保证对象的唯一性
枚举实例的创建是线程安全的,并且在任何情况下都是一个单例,即使反序列化(不用重写readResolve方法)也不会重新生成实例
public enum CEO4Enum { CEO4; public void doSomething(){ System.out.println("do sth."); } }
使用容器实现单例模式
public class CEOManager { private static Map<String, Object> mCeo = new HashMap<>(); public static void registerService(String key, Object ceo){ if (!mCeo.containsKey(key)){ mCeo.put(key,ceo); } } public static Object getService(String key){ return mCeo.get(key); } }
在程序的初始化中,将多种单例类型注入到统一的管理类中,通过key获取对象对应类型的对象