Android学习之设计模式---单例模式
1 单例模式初识:
1.1 单例模式:
单例模式是指确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例;那为什么要有单例模式呢?比如我们在代码中有一个网络请求的实现类,那在业务需求的场景中,会有频繁的网络请求,那此时如果有一个网络请求,就去new一个网络请求的实现类,这样可能会造成资源的浪费,所以引入了单例模式;
1.2 单例模式的特征或者说创建一个单例模式类的要求:
- 构造方法不对外开放的(private修饰);
- 建立一个类静态变量,持有一个自己的实例;
- 通过一个静态方法或者枚举返回单列类的对象;
- 注意多线程的场景也要保证单例;
- 如果单例可以序列化,那要注意单例对象在反序列化时不会重新创建对象;
1.3 单例模式的两种类型:
1.3.1 饿汉式单例模式
public class singletonMode {
private singletonMode (){} //构造方法private修饰,不对外开放
private static mSingleton = new singletonMode();//静态变量实例化
public static singletonMode getInstance(){//通过一个静态方法返回单列类的对象
retrun mSingleton;
}
上面为饿汉式单例模式的实现方式,从代码中可以看出,在类加载的时候mSingleton就已经被实例化了,无论用不用都会被加载,所以可能会造成资源浪费或者加载缓慢(比如说构造方法中做的操作比较多);但是它是线程安全的;
1.3.2 懒汉式单例模式
针对饿汉式单例模式的缺点,则引出了如下的懒汉式单例模式:
public class singletonMode2 {
private singletonMode2 (){} //构造方法private修饰,不对外开放
private static mSingleton2 = null();//静态变量
public static singletonMode2 getInstance(){//通过一个静态方法返回单列类的对象
if(mSingleton2 == null)//当实例对象为null的时候才去new
mSingleton2 = new singletonMode2();
retrun mSingleton2;
}
以上可以解决饿汉式单例模式在类加载的时候就实例化静态变量,引起资源浪费的问题,此处是在用的时候通过getInstance去拿时,才去new;但是通过仔细观察,懒汉式单例模式存在线程不安全问题,所以需要进一步优化;
优化方式一:对getInstance静态方法添加synchronized关键字
public class singletonMode3 {
private singletonMode3 (){} //构造方法private修饰,不对外开放
private static mSingleton3 = null();//静态变量
public static synchronized singletonMode3 getInstance(){//通过一个静态方法返回单列类的对象
if(mSingleton3 == null)//当实例对象为null的时候才去new
mSingleton3 = new singletonMode3();
retrun mSingleton3;
}
相比之下,线程安全提高,但是synchronized是加在静态方法上的,同步锁的颗粒度有点大,所以进一步优化;
优化方式二:这就引出了 双重校验DCL
public class singletonMode4 {
private singletonMode4 (){} //构造方法private修饰,不对外开放
private static mSingleton4 = null();//静态变量
public static singletonMode4 getInstance(){//通过一个静态方法返回单列类的对象
if(mSingleton4 == null){//第一层校验
synchronized(singletonMode4 .class){
if(mSingleton4 == null){//第二层校验
mSingleton4 = new singletonMode4();
}
}
}
retrun mSingleton4;
}
采用方式二则降低了同步锁的颗粒度,同时也考虑了线程安全问题;
注意:但是在JAVA虚拟机中,大家都知道mSingleton4 = new singletonMode4()一句代码它主要包括三个内容:
- mSingleton4 实例分配对象
- 调用singletonMode4的构造方法,初始化成员字段
- 将singletonMode4对象赋值给mSingleton4
由于在JDK中会进行指令重排,所以有可能会导致DCL失效问题,所以在JDK1.5后引入了volatile禁止指令重排,进而保证了DCL双重检测的有效性;
2 饿汉式与懒汉式单例模式的应用:
饿汉式
在类加载的时候就会实例化对象,无论是否会用到这个对象,都会加载。如果在构造方法里写了性能消耗较大,占时较久的代码,那么就会在启动的时候感觉稍微有些卡顿。
懒汉式
是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量。使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
在实际应用中看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
3 单例模式扩展
3.1 单例模式的其他实现方式:
方式一:静态内部类单例模式:
public class singletonMode5 {
private singletonMode5 (){} //构造方法private修饰,不对外开放
private static class singletonModeInner {
private static mSingleton5 = new singletonMode5();
}
public static singletonMode5 getInstance(){//通过一个静态方法返回单列类的对象
retrun singletonMode5.singletonModeInner.mSingleton5 ;
}
}
方式二:使用枚举:
public class Single {
private Single(){}
public enum SingleEnum {
singleHandler;
private Single single;
private SingleEnum () {
single = new Single();
}
public Single getSingle() {
return single;
}
}
public static Single getInstacne() {
return SingleEnum.singleHandler.getSingle();
}
}
对于以上两种实现方式,枚举是线程安全的。另外还有一些比如说通过容器的方式来实现单例,有兴趣的可以进一步了解一下;
3.2 可序列化的单例类
如果我们的类是可序列化的,那么在反序列化时会破坏单例,生成新的单列类;
private Object readResolve(){
System.out.println("read resolve");
return instance;//返回之前定义的单例类对象
}
这中情况,可以通过重写readResolve()方法,此方法中返回了单例类的对象。具体readResolve解析可参考这篇文章:https://blog.csdn.net/weixin_45433031/article/details/115364766?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242
小结:本章节了解了设计模式中的单例模式,单例模式在我们的实际开发中是很常见的,大家可以根据自己业务需求,选择不同的实现方式,那重点关注一下DCL双重检测;