设计模式之单例模式实现的几种方式

单例模式详解
本文详细介绍了单例模式的概念、特点及多种实现方式,包括饿汉式、懒汉式、双重检查锁定、静态内部类和枚举等,并分析了各自的优缺点。

单例模式的定义

确保某个类有且只有一个实例,并且能够自行实例化提供给调用者。

单例模式的使用场景

通常有以下的原因会让你使用单例:
1.这个对象创建需要消耗很多的资源;
2.这个对象不能存在多个对象,多个对象容易造成数据错误或者其他原因。

单例模式的特点

1.构造函数不对外开放,一般为private;
2.通过一个静态方法或者枚举返回单例对象;
3.确保在任何情况下都只有一个实例,尤其是多线程情况下;
4.确保在反序列化时不会重新创建对象。

-最直接的实现方式(大家通常叫这种方式为:饿汉,原因就是静态初始化)
优点:省心
缺点:无法控制创建的时间,在你没使用前就已经创建好了对象

//Java方式
public class Coder {
    private static Coder instance = new Coder();
    private Coder(){}
    public static Coder getInstance() {
        return instance;
    }
}
//Kotlin方式就很简单
object Coder{

}

-懒汉方式
优点:保证了多线程下的唯一性
缺点:每次加载都需要同步,有点耗时

//Java
public class Coder {
    private static Coder instance;
    private Coder(){}
    public static synchronized Coder getInstance() {
        if (instance == null) {
            instance = new Coder();
        }
        return instance;
    }
}
//Kotlin版本这里有点不一样,我这里是自己手写的,直接翻译Java的方式和我写的不一样
//所以读者参考就好,毕竟实现的方式不是唯一的
class Coder private constructor() {
    companion object {
        private var instance: Coder ?=null

        @Synchronized
        fun getInstance(): Coder {
            if (instance == null) {
                instance = Coder()
            }
            return instance!!
        }
    }
}
//Kotlin版本在不同文件格式的调用方式不一样,在Kotlin文件中调用
Coder.getInstance()
//在Java文件调用
Coder.Companion.getInstance();

-Double Check Lock(DCL),我直译双重锁检查
优点:在需要时初始化,并且增加了线程安全
缺点:第一次加载稍慢,在一些特殊情况下还是会创建多个对象

//Java版本
public class Coder {
    private static Coder instance = null;
    private Coder(){}
    public static Coder getInstance() {
        if (instance == null) {
            synchronized (Coder.class){
                if (instance == null) {
                    instance = new Coder();
                }
            }
        }
        return instance;
    }
}

书中说明为什么会出现多个对象的情况:

假设线程A执行到了instance = new Coder();这一行,实际上并不是一个原子操作,这行代码会被翻译成多个汇编指令:
1.给instance的实例分配内存;
2.调用Coder()的构造函数,初始化成员字段;
3.将instance对象指向分配的内存空间(也就是instance指向了对象地址,不在是null了)。

由于Java编译器语序处理器乱序执行,以及JDK1.5之前JMM(Java Memory Model,即Java内存模型)中Cache、寄存器到主内存回写顺序的规定,上面的2和3的顺序是无法保证的,也就是最终的执行顺序可能为1-2-3或者1-3-2。如果是后者,在3执行完毕、2未执行之前,切换到了线程B,这时候instance因为已经在线程A执行过了第三点,instance已经是非空了,所以线程B直接返回了instance对象,再使用的时候就会出错。

官方注意到这个问题,具体化了volatile关键字,所以我们需要修改一下上面的实现

//最终版本
public class Coder {
    private static volatile Coder instance = null;
    private Coder(){}
    public static Coder getInstance() {
        if (instance == null) {
            synchronized (Coder.class){
                if (instance == null) {
                    instance = new Coder();
                }
            }
        }
        return instance;
    }
}
//Kotlin
class Coder private constructor() {

    companion object {
        @Volatile
        private var instance: Coder? = null

        fun getInstance(): Coder {
            if (instance == null) {
                synchronized(Coder::class.java) {
                    if (instance == null) {
                        instance = Coder()
                    }
                }
            }
            return instance!!
        }
    }

}

-静态内部类单例(比较推荐的实现方式)
优点:效率高,简单,线程安全
缺点:水平有限,无法总结(手动苦笑)

//Java
public class Coder {
    private Coder(){}
    private static class CoderHolder{
        private static final Coder INSTANCE = new Coder();
    }

    public static Coder getInstance() {
        return CoderHolder.INSTANCE;
    }
}
//Kotlin仅供参考
class Coder private constructor(){

    companion object {
        fun getInstance():Coder{
            return CoderHolder.instance
        }
    }

    private object CoderHolder {
        val instance = Coder()
    }

}

-枚举实现
优点:简单、与其他方式相比反序列化方式下也能够保持唯一性
缺点:应该是内存占用比其他方式多一些(未用实例验证)

//Java
public enum Coder {
    INSTANCE;
    public void doSomething(){
        System.out.println("do sth...");
    }
}
//Kotlin
enum class Coder {
    INSTANCE;

    fun doSomething() {
        println("do sth...")
    }
}

-最后是采用容器实现的单例

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

    private SingletonManager() {

    }

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

    public static Object getInstance(String key) {
        if (holderMap.containsKey(key)) {
            return holderMap.get(key);
        }
        return null;
    }
}

最后

其实还差两个部分是分析Android系统中的单例和Kotlin自有的一些写法,这就之后补吧。
无论我们采用哪种方式实现,核心原理都是将构造函数私有化,通过静态方法返回唯一的实例,要注意多线程和反序列化。
选择哪种方式还是要根据自身遇到的情况来分析了。

本文是读《Android源码设计模式解析与实战》后总结的,如侵权请联系2293373743@qq.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值