2022面试Android之单例模式

单例模式大致可以分为两类,懒汉模式和饿汉模式,但是不必在意是懒还是饿,还是要明白他们的原理和区别。(什么是懒汉,就是类加载了之后,并没有实例化单例,而是延后到第一次使用的时候;什么事恶汉,就是类加载了,就实例化单例了。)

本文所举例均为线程安全的单例模式。

直接看代码和注释:

1、双重加锁的单例模式(懒汉模式)

这种模式也是作者之前最常使用的模式,因为代码好理解,并且是lazyload模式,不会产生垃圾对象。基本面试的时候也会首先回答这种模式,这种模式可以延伸出好多知识点,包括volatile的作用,synchronized锁。

/**
 * 双层加锁的单例模式
 * */
public class SingletonTest3 implements Test{
    /**
     * instance使用volatile修饰,防止线程之间出现脏数据,导致线程A已经执行完instance = new SingletonTest3() ;
     * 后线程B拿到锁,但是判断instance还是null,而导致instance初始化两次。
     */
    private volatile static SingletonTest3 instance = null ;

    private SingletonTest3(){
        Log.d("gggl" , "SingleTest3 初始化了") ;
    }

    public static SingletonTest3 getInstance(){
        //首先进行instance的null判断,为了在没有线程竞争的情况下提高速度不必进行锁的竞争。
        if (instance == null) {
            //加锁防止线程竞争
            synchronized (SingletonTest3.class) {
                //二次判断,这里是必须的,因为第一个null判断不是线程安全的。
                if (instance == null) {
                    instance = new SingletonTest3() ;
                }
            }
        }
        return instance ;
    }

    @Override
    public void test() {
        Log.d("gggl" , "SingletonTest3 初始化了") ;
    }
}

2、static final 修饰成员变量instance(恶汉模式)

这种模式下,当我们的class被加载到内存中后,就会调用构造函数,如果你的单例项目启动后就一直需要的话,采用这种方式没有任何问题,简单方便。但是如果你的单例是项目启动后需要某些动作触发才需要的话,不建议采用这种方式,浪费一丢丢内存。其实也有点吹毛求疵了哈,不过这是面试,认真点。-^-

import com.example.interview.Test;

public class SingletonTest1 implements Test {
    private SingletonTest1(){
        Log.d("gggl" , "SingletonTest1 初始化了")  ;
    }
    private static final SingletonTest1 instance = new SingletonTest1() ;

    public static SingletonTest1 getInstance() {
        return instance ;
    }
    public void test(){
        Log.d("gggl" , "SingletonTest1 say hello!!!!") ;
    }
}

3、静态内部类模式(懒汉模式)

静态内部类模式。这种模式略显负责,作者也是不喜欢使用,因为我基本都是采用第一种的双重null判断模式。但是没办法出去面试,都是看看。


public class SingletonTest1 implements Test {
    private SingletonTest1(){
        Log.d("gggl" , "SingletonTest1 初始化了")  ;
    }

    public static SingletonTest1 getInstance() {
        return SingletonHander.singleton ;
    }
    public void test(){
        Log.d("gggl" , "SingletonTest1 say hello!!!!") ;
    }
    private static class SingletonHander{
        private static final SingletonTest1 singleton = new SingletonTest1 ();
    }
}

测试代码:

try {
            Class.forName("com.example.interview.designpattern.SingletonTest1" , true , getClassLoader()) ;
            Log.d("gggl" , "SingletonTest1加载完毕") ;
            Class.forName("com.example.interview.designpattern.SingletonTest2" , true , getClassLoader()) ;
            Log.d("gggl" , "SingletonTest2加载完毕") ;
            Class.forName("com.example.interview.designpattern.SingletonTest3" , true , getClassLoader()) ;
            Log.d("gggl" , "SingletonTest3加载完毕") ;
            SingletonTest1.getInstance().test();
            SingletonTest2.getInstance().test();
            SingletonTest3.getInstance().test();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

这里首先对SingletonTest的几个类进行了加载(注:Class.forName进行加载类的时候顺便执行了初始化的一些任务,具体执行到哪些步骤作者没有具体看jdk代码,但是static成员变量和代码块都是在这个阶段执行的)。然后分别调用SingletonTest.getInstance().test()方法。下面我们来看执行结果。

2022-09-16 10:16:01.081 11710-11710/com.example.interview D/gggl: SingletonTest1 初始化了
2022-09-16 10:16:01.081 11710-11710/com.example.interview D/gggl: SingletonTest1加载完毕
2022-09-16 10:16:01.082 11710-11710/com.example.interview D/gggl: SingletonTest2加载完毕
2022-09-16 10:16:01.082 11710-11710/com.example.interview D/gggl: SingletonTest3加载完毕
2022-09-16 10:16:01.082 11710-11710/com.example.interview D/gggl: SingletonTest1 say hello!!!!
2022-09-16 10:16:01.082 11710-11710/com.example.interview D/gggl: SingletonTest2 初始化了
2022-09-16 10:16:01.082 11710-11710/com.example.interview D/gggl: SingletonTest2 say hello!!!!
2022-09-16 10:16:01.082 11710-11710/com.example.interview D/gggl: SingleTest3 初始化了
2022-09-16 10:16:01.082 11710-11710/com.example.interview D/gggl: SingletonTest3 初始化了

大家看到了SingletonTest1也就是饿汉模式的第一种(private static final SingletonInstance1 = new SingletonInstance1())在类加载的时候就执行了构造函数,这就是区别,面试知识点。

总结:

其实以上三种模式大家在开发的时候只需固定化使用1或者3即可,第二种因为不是lazyload模式所以尽量不要使用,好的编码习惯也是需要点滴养成的。而针对面试知道上面三种模式足够了,至于那种线程不安全的最简单的模式就不要往出写了,没任何意义。

猜你喜欢

转载自blog.csdn.net/mldxs/article/details/126885496