单例模式的好处
1)只有一个对象,内存开支少、性能好
2)避免对资源的多重占用
3)在系统设置全局访问点,优化和共享资源访问
为什么使用单例模式
单例模式是我们平时adroid 开发中经常用到的设计模式,那么为什么要使用单例模式?
当全局只需要一个实例化对象的时候,我们就需要使用单例模式,比如Glide这个库就使用了单例模式,因为Glide里面本身包含了很多重量级的成员变量,如果再创建多个对象就会造成严重的资源消耗,导致内存不够的情况出现,而且Glide 本身只需要生成一个实例化对象,其他地方就可以实现重复的利用,这个时候我们就需要使用单列模式。
单例模式的特征
1)构造方法私有化,一般不对外开放 (private)
2)实例化对象静态化、私有化
3)通过一个静态方法或者枚举返回单列类的对象
4)多线程的时候注意线程安全
5)确保单例类的对象在反序列化时不会重新创建对象
单例模式的实现方式
1、饿汉模式(线程安全)
class SingleInstance {
private static SingleInstance singleInstance = new SingleInstance();
private SingleInstance() {
}
public static SingleInstance getInstance() {
return singleInstance;
}
}
优点
由于使用了static关键字,保证了在引用这个变量时,关于这个变量的所以写入操作都完成,所以保证了JVM层面的线程安全
缺点
不能实现懒加载,造成空间浪费,如果一个类比较大,我们在初始化的时就加载了这个类,但是我们长时间没有使用这个类,这就导致了内存空间的浪费。
2、懒汉模式(线程不安全)
class SingleInstance {
private static SingleInstance singleInstance;
private SingleInstance() {
}
public static SingleInstance getInstance() {
if (singleInstance == null) {
singleInstance = new SingleInstance();
}
return singleInstance;
}
}
懒汉模式可以实现懒加载,但是在多线程的情况下可能存在创建多个对象的风险,是一种线程不安全的单例模式
3、DCL(Double Check Lock)模式(线程安全)
class SingleInstance {
private volatile static SingleInstance singleInstance;
private SingleInstance() {
}
public static SingleInstance getInstance() {
if (singleInstance == null) {
synchronized (SingleInstance.class) {
if (singleInstance == null) {
singleInstance = new SingleInstance();
}
}
}
return singleInstance;
}
}
双重校验枷锁模式可以避免线程不安全的情况同时也可以实现懒加载,但是这里的代码理解起来可能有点困难,接下来我们分析一下代码这样设计的原因:
首先这里为什么判空两次呢?
第一次判空是为了避免不必要的同步,
第二次判空则是为了在null的情况下创建实例。
为什么要在变量上面加一个volatile字段?
在java中singleInstance=new SingleInstance()语句并不是一个原子操作,代码会被编译成多条编译指令,这里大致做了三件事情
1)给SingleInstance的实例分配内存
2)调用SingleInstance的构造方法SingleInstance(),初始化成员字段
3)将SingleInstance的对象指向分配的内存空间(此时singleInstance就不是null了)
这里看起来没什么问题,但是要知道CPU是可能会乱序执行指令的,因为为了效率,CPU可能会将没有必然联系的指令重排序,即3在2之前执行了,当然对于单线程这样的操作是不会影响运行,而当有多个线程,而又在此时恰好有线程B调用了getInstance()方法,第一个if,因为singleInstance指向的空间已经不为null,所以直接返回,那毫无疑问当引用singleInstance的时候,由于没有完全初始化,就可能会产生异常或者和实际不符。那怎么解决呢,答案就是给singleInstance添加volatile。
volatile有两个特性:可见性和有序性
什么是可见性,即线程A修改变量x,那么对于线程B应该立即可以获取到信息x被修改,并重新读取
什么是有序性,即禁止许指令重排序
4、静态内部类模式(线程安全)
class SingleInstance {
private static class Internal{
private static SingleInstance singleInstance = new SingleInstance();
}
private SingleInstance() {
}
public static SingleInstance getInstance() {//延迟加载
return Internal.singleInstance;
}
}
静态内部类实现单例模式是线程安全的同时可以实现懒加载,但是会存在反序列化和反射的问题
5、枚举模式(线程安全)
enum SingleInstance {
INSTANCE;
void test(){
}
}
枚举类型实现单例模式是线程安全的,不存在反序列化和反射的问题
破坏单例模式的方法及解决办法
1、除枚举方式外, 其他方法都会通过反射的方式破坏单例,反射是通过调用构造方法生成新的对象,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例, 则阻止生成新的实例,解决办法如下:
private SingleInstance(){
if (singleInstance !=null){
throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取");
}
}
2、如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例,所以我们可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。
public Object readResolve() throws ObjectStreamException {
return Internal.singleInstance;
}