Android_Question icon indicating copy to clipboard operation
Android_Question copied to clipboard

单例模式有几种写法以及各自的优劣?

Open whatshappen opened this issue 5 years ago • 2 comments

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;
}

whatshappen avatar Apr 02 '19 15:04 whatshappen

多线程下,不加volatile的双检锁是没有意义的。 在给单例对象初始化的过程中,jvm做了下面3件事: 1.给单例对象分配内存 2.调用构造函数 3.将单例对象指向分配的内存空间 由于jvm的"优化",指令2和指令3的执行顺序是不一定的,当执行完指定3后,此时的单例对象就已经不在是null的了,但此时指令2不一定已经被执行。 假设线程1和线程2同时调用单例对象的方法,此时线程1执行完指令1和指令3,线程2抢到了执行权,此时单例对象是非空的。 所以线程2拿到了一个尚未初始化的单例对象,此时线程2调用这个单例就会抛出异常。 所以需要volatile 禁止指令重排序

zsmjhtn avatar Apr 15 '19 01:04 zsmjhtn

多线程下,不加volatile的双检锁是没有意义的。 在给单例对象初始化的过程中,jvm做了下面3件事: 1.给单例对象分配内存 2.调用构造函数 3.将单例对象指向分配的内存空间 由于jvm的"优化",指令2和指令3的执行顺序是不一定的,当执行完指定3后,此时的单例对象就已经不在是null的了,但此时指令2不一定已经被执行。 假设线程1和线程2同时调用单例对象的方法,此时线程1执行完指令1和指令3,线程2抢到了执行权,此时单例对象是非空的。 所以线程2拿到了一个尚未初始化的单例对象,此时线程2调用这个单例就会抛出异常。 所以需要volatile 禁止指令重排序

volatile 有两个功能:一是防止其前后的重排序,而是保证数据的可见性

whatshappen avatar Apr 19 '19 02:04 whatshappen