单例模式的定义
确保某个类有且只有一个实例,并且能够自行实例化提供给调用者。
单例模式的使用场景
通常有以下的原因会让你使用单例:
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