Android_Question
Android_Question copied to clipboard
单例模式有几种写法以及各自的优劣?
1.饿汉式:
public class SingleInstance {
private static SingleInstance mInstance = new SingleInstance();
private SingleInstance(){}
public static SingleInstance getInstance(){
return mInstance;
}
}
缺点:存在内存损耗问题,如果当前类没有用到也会被实例化
2.懒汉式:
public class SingleInstance {
private static SingleInstance mInstance = null;
private SingleInstance(){}
public static SingleInstance getInstance(){
if(mInstance==null){
synchronized (SingleInstance.class){
if(mInstance==null){
mInstance = new SingleInstance();
}
}
}
return mInstance;
}
}
缺点:加了synchronized锁会影响性能 有次被问到为什么要有两次空判断? 第一次空判断和好理解,可以很大程度上减少锁机制的次数; 第二次判空是因为,如果a,b两个线程都到了synchronized处,而假设a拿到了锁,进入到代码块中创建了对象,然后释放了锁,由于b线程在等待锁,所以a释放后,会被b拿到,因此此时判空就保证了实例的唯一性。
3.静态内部类:
public class SingleInstance {
private SingleInstance(){}
public static SingleInstance getInstance(){
return Builder.mInstance;
}
private static class Builder{
private static SingleInstance mInstance = new SingleInstance();
}
}
优点:解决了内存浪费问题,同时也避免了加锁性能问题 为什么这种写法是线程安全的? 因为类加载过程是安全的,而静态变量是随着类的加载进行初始化的。
4.枚举形式:
public enum SingleInstance {
INSTANCE;
}
优点:不存在反射和反序列化的问题。 缺点:通过查看枚举类生成的class文件发现,有多少变量,就会在静态代码块中创建多少对象,所以不建议使用。
定义一些有意义的常量,如果不用枚举,怎么解决? 可以使用注解的形式,例如:
@IntDef({DataType.INT, DataType.STRING, DataType.FLOAT, DataType.DOUBLE, DataType.OBJECT})
public @interface DataType {
int INT = 0;
int STRING = 1;
int FLOAT = 2;
int DOUBLE = 3;
int OBJECT = 4;
}
多线程下,不加volatile的双检锁是没有意义的。 在给单例对象初始化的过程中,jvm做了下面3件事: 1.给单例对象分配内存 2.调用构造函数 3.将单例对象指向分配的内存空间 由于jvm的"优化",指令2和指令3的执行顺序是不一定的,当执行完指定3后,此时的单例对象就已经不在是null的了,但此时指令2不一定已经被执行。 假设线程1和线程2同时调用单例对象的方法,此时线程1执行完指令1和指令3,线程2抢到了执行权,此时单例对象是非空的。 所以线程2拿到了一个尚未初始化的单例对象,此时线程2调用这个单例就会抛出异常。 所以需要volatile 禁止指令重排序
多线程下,不加volatile的双检锁是没有意义的。 在给单例对象初始化的过程中,jvm做了下面3件事: 1.给单例对象分配内存 2.调用构造函数 3.将单例对象指向分配的内存空间 由于jvm的"优化",指令2和指令3的执行顺序是不一定的,当执行完指定3后,此时的单例对象就已经不在是null的了,但此时指令2不一定已经被执行。 假设线程1和线程2同时调用单例对象的方法,此时线程1执行完指令1和指令3,线程2抢到了执行权,此时单例对象是非空的。 所以线程2拿到了一个尚未初始化的单例对象,此时线程2调用这个单例就会抛出异常。 所以需要volatile 禁止指令重排序
volatile 有两个功能:一是防止其前后的重排序,而是保证数据的可见性