设计模式之单例模式
一、介绍:
单例模式是应用最广的模式之一;在应用这个模式时,单例对象的类必须保证只有一个实例的存在;许多时候,整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为;如在一个应用中,应该只有一个ImageLoader实例,这个ImageLoder中有含有线程池、缓存系统、网络请求等,很消耗资源,很消耗资源,因此,没有理由让他构造多个实例。这种不能自由构造对象的情况,其实就是单例模式的使用场景;
二、定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例;
三、单例模式使用的场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如:创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时就要考虑使用单例模式;
四、单例模式的关键点
1、类的构造函数不对外开放,一般为Private;
2、通过一个静态方法或者枚举返回单例类对象;
3、确保单例类对象有且只有一个,特别是在多线程的环境下;
4、确保单例类对象在反序列化时不会重新构建对象;
五、单例类的简单实例 : 饿汉模式,在声明静态对象时就进行初始化
例:一个公司只有一个CEO,可以有几个VP、无数个员工,但CEO只有一个:
/**普通员工*/
public class Staff{
public void work(){
//工作
}
}
/** 副总裁 */
public class VP extends Staff{
@override
public void work{
//副总裁工作,管理下面的经理
}
}
/** CEO 饿汉单例模式*/
public class CEO extends Staff{
private static final CEO mCeo = new CEO();
//构造私有函数
private CEO(){};
//定义公有的静态函数,对外暴露获取单例对象的接口
public static CEO getCeo(){
retrun mCeo ;
}
@override
public void work(){
//CEO工作管理VP
}
}
/**公司类*/
public class Company{
private List<Staff> allStaffs = new ArrayList<>();
public void addStaff(Staff per){
allStaffs.add(per);
}
public void showAllStaffs(){
for(Staff staff:allStaffs){
system.out.println(“Obj:” + staff.toString);
}
}
}
/**测试*/
public class SingletonDemo {
public static void main(String[] args) {
Company mCompany = new Company(); //一个公司
Staff mStaff1 = CEO.getCeo(); //一个CEO,单例
Staff mStaff2 = CEO.getCeo();
mCompany.addStaff(mStaff1);
mCompany.addStaff(mStaff1);
//通过new 创建VP
Staff vp1 = new VP();
Staff vp2 = new VP();
mCompany.addStaff(vp1);
mCompany.addStaff(vp2);
//通过new创建staff
Staff staff1 = new Staff() ;
Staff staff2 = new Staff();
Staff staff3 = new Staff();
mCompany.addStaff(staff1);
mCompany.addStaff(staff2);
mCompany.addStaff(staff3);
mCompany.showAllStaff();
}
}
测试结果如下:
Obj : sl.com.designmodedemo.a_singleton.CEO@28d93b30
Obj : sl.com.designmodedemo.a_singleton.CEO@28d93b30
Obj : sl.com.designmodedemo.a_singleton.VP@1b6d3586
Obj : sl.com.designmodedemo.a_singleton.VP@4554617c
Obj : sl.com.designmodedemo.a_singleton.Staff@74a14482
Obj : sl.com.designmodedemo.a_singleton.Staff@1540e19d
Obj : sl.com.designmodedemo.a_singleton.Staff@677327b6
从结果中可以看出:CEO类是由CEO.getCeo()获取的实例对象,而这个CEO的对象是静态对象,并且在声明的时候就已经初始化了,就保证了这个CEO对象的唯一性,从输出结果也可以发现,两次CEO的输出对象都是一样的;而其他Staff、VP对象都是不同的,
这种实现方式的核心在于:将CEO类的构造方法私有化,使得外部程序不能通过构造函数获取实例,而CEO则通过一个静态方法返回静态对象;
六、单例模式的其他实现方式
一、懒汉模式:懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化
public class Singleton {
private static Singleton instance ;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton() ;
}
return instance ;
}
}
优点:单例只有在使用的时候才会被实例化,在一定程度上节约了资源;
缺点:第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销;这种模式一般不建议使用;
二、Double Check Lock (DCL)实现单例:
public class Singleton {
private static Singleton instance ;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){ //判空,避免不必要的同步
synchronized (Singleton.class){
if (instance == null){ //判空,创建实例
instance = new Singleton() ;
}
}
}
return instance;
}
}
优点:第一次执行getInstance时才会进行实例化,效率高,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。
缺点:第一次执行稍慢,也由于Java内存模型的原因偶尔会失败,在高并发的环境下也有一定的缺陷,虽然发生概率很小;
DCL模式是使用最多的单例模式,他能够在需要时才实例化单例对象,并且在绝大数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK6版本下使用,否则,这种方式一般能够满足需求;
三、静态内部类单例模式
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 something");
}
五、使用容器实现单例模式
public class SingletonManager{
private static Map<String,Object> objMap = new HashMap<>();
private SingletonManager(){};
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);
}
}
总结
单例模式是使用频率很高的模式,但是,由于在客户端通常没有高并发的情况,因此,选择哪种实现方法并不会有太大的影响,即使如此,出于效率考虑,一般也是使用DCL方式和内部类单例的实现形式;
优点
1、单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建和销毁时,而且创建和销毁的性能又无法优化,单例模式的优势就非常明显;
2、单例模式只生产一个实例,减少了系统的新能开销,当一个对象的场所需要较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决;
3、单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对一个资源文件的同时写操作;
4、单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有的数据表的映射管理;
缺点
1、单例模式一般没有借口,扩展困难,如要扩展,除了修改代码基本上没有第二种途径可以实现;
2、单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传给到单例对象的Context最好是Application Context ;