Android面试题:单例模式

本文探讨了Android面试中常见的单例模式问题,包括懒汉、饿汉、静态内部类和枚举四种实现方式,重点分析了懒汉模式的优化,特别是双重校验锁(DCL)和volatile关键字在保证线程安全和防止指令重排中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考链接
单例模式特征:

单例模式(Singleton Pattern)是一种简单的对象创建型模式。该模式保证一个类仅有一个实例,
并提供一个访问它的全局访问点。

所以要实现单例模式,要做到以下几点:
    
1.构造方法不对外开放,一般是private,杜绝使用构造器创建实例。
2.一般通过一个静态方法或者枚举返回单例类对象
(也就是需要自身创建唯一的一个实例,并提供一个全局访问入口)
3.注意多线程的场景(多线程会出现线程不安全的问题)
4.如果单例对象可以被序列化,要注意单例对象在反序列化的时候不会重新创建对象

一、懒汉单例模式

class MyTest{
    static class Singleton1 {
        //私有的静态变量
        private static Singleton1 mIntance = null;

        //私有的静态方法
        private Singleton1() {
            Log.i("gjw", "Singleton1: 懒汉模式单例");
        }

        //暴露一个共有的静态方法
        public static Singleton1 getInstance() {
            if (mIntance == null) {
                mIntance = new Singleton1();
            }
            return  mIntance;
        }
    }
}


调用测试:

class MyTest{
  static class Singleton2 {
        private static Singleton2 mSingle = new Singleton2();
        private Singleton2() {
        }
        public static Singleton2 getInstance() {
            return mSingle;
        }
    }
    }
当调用 Singleton1.getInstance();的时候,有可能上一个对象的确是没有创建好,又创建了一对象
所以懒汉单例模式缺点:线程不安全

懒汉单例模式优化
使用synchronized保证线程安全

class MyTest{
    static class Singleton1 {
        //私有的静态变量
        private static Singleton1 mIntance = null;

        //私有的静态方法
        private Singleton1() {
            Log.i("gjw", "Singleton1: 懒汉模式单例");
        }

        //暴露一个共有的静态方法
        public synchronized static Singleton1 getInstance() {//使用synchronized保证线程安全
            if (mIntance == null) {
                mIntance = new Singleton1();
            }
            return  mIntance;
        }
    }
}

该方式是使用synchronized关键字进行加锁,保证了线程安全性。
优点:在第一次调用才初始化,避免了内存浪费。
缺点:对获取实例方法加锁,大大降低了并发效率。

由于加了锁,对性能影响较大,不推荐使用。

懒汉单例模式再次优化
双重校验(DCL)保证对象单例,并且使用synchronized保证线程安全

class MyTest{
    static class Singleton1 {
        //私有的静态变量
        private static Singleton1 mIntance = null;

        //私有的静态方法
        private Singleton1() {
            Log.i("gjw", "Singleton1: 懒汉模式单例");
        }

       //暴露一个共有的静态方法
        public static Singleton1 getInstance() {
            if (mIntance == null) {
                synchronized (Singleton1.class) {//使用synchronized保证线程安全
                    if (mIntance == null) {//双重校验(DCL)保证对象单例
                        mIntance = new Singleton1();
                    }
                }
            }
            return mIntance;
        }
    }
}

在执行下面代码的时候,JVM的流程是
 synchronized (Singleton1.class) {
                    if (mIntance == null) {//双重校验(DCL)保证对象单例
                        mIntance = new Singleton1();
                        
                        1.mIntance 实例分配对象
                        2.Singleton1 调用构造方法初始化成员字段
                        3.Singleton1 对象赋值给mIntance 
                    }

但是上面的三个步骤在jdk虚拟机里面可能是乱序的(指令重排),
就不能保证第二步一定在第三步之前进行

所以会出现双重校验(DCL)失效======这个面试的时候会经常问这个问题

所以使用volatile关键字可以防止指令重排
 private volatile static Singleton1 mIntance = null;
 
这种方式利用了volatile修饰符的线程可见性(被一个线程修改后,其他线程立即可见),
即保证了懒加载,又保证了高性能,所以推荐使用。

懒汉单例模式最终优化结果(使用volatile)

volatile特点
1.线程间可见性
2.禁止指令重排

class MyTest{
    static class Singleton1 {
        //私有的静态变量
        private volatile  static Singleton1 mIntance = null;

        //私有的静态方法
        private Singleton1() {
            Log.i("gjw", "Singleton1: 懒汉模式单例");
        }

       //暴露一个共有的静态方法
        public static Singleton1 getInstance() {
            if (mIntance == null) {
                synchronized (Singleton1.class) {//使用synchronized保证线程安全
                    if (mIntance == null) {//双重校验(DCL)保证对象单例
                        mIntance = new Singleton1();
                    }
                }
            }
            return mIntance;
        }
    }
}

二、饿汉单例模式

    static class Singleton2 {
        private static Singleton2 mSingle = new Singleton2();

        private Singleton2() {

        }

        public static Singleton2 getInstance() {
            return mSingle;
        }
    }
   
饿汉式是利用类加载机制来避免了多线程的同步问题,所以是线程安全的。
优点:未加锁,执行效率高。
缺点:类加载时就初始化实例,造成内存浪费。

如果对内存要求不高的情况,还是比较推荐使用这种方式。

三、静态内部类单例模式

 static class Singleton3 {
        private Singleton3() {

        }

        private static class Holder {
            private static final Singleton3 INSTANCE = new Singleton3();
        }

        public static final Singleton3 singleton3() {//可以延迟加载
            return Holder.INSTANCE;
        }
    }


优点:线程安全、效率高而且可以延迟加载

缺点:该模式利用了静态内部类延迟初始化的特性,来达到与双重校验锁方式一样的功能。
由于需要借助辅助类,并不常用。

四、使用枚举单例模式

public enum SingletonEnum {
    INSTANCE;
    public void method() {
        System.out.println("枚举类中定义方法!");
    }
}

该方式利用了枚举类的特性,不仅能避免线程同步问题,还防止反序列化重新创建新的对象。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式。

但由于这种编码方式还不能适应,所以实际工作中很少使用。

五、使用容器来使用单例

  static class SingletonManager {
        private static Map<String, Object> map = new HashMap<>();

        public static void registerService(String key, Object instance) {
            if (!map.containsKey(key)) {
                map.put(key, instance);
            }
        }

        public static Object getService(String key) {
            return map.get(key);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值