单例模式之Java和Kotlin版

微信扫一扫,分享到朋友圈

单例模式之Java和Kotlin版

前段时间在封装Kotlin版的MVP+RxJava+Retrofit项目框架的时候,需要多处使用单例模式,但是Kotlin的单例模式却是接触不够的,所以笔者抽空学习了一波。对照着Java版,总结了常用的五大单例模式,分别为: 饿汉模式
懒汉模式(线程安全版、线程不安全版)
双重检测模式
静态内部类模式
。下面我将一一分析下这五种模式的写法和优缺点,都是对比着Java版分析的。

这里大致解释下单例模式是什么以及怎么用。笔者认为:从 Android
开发的角度来说,单例模式也就是在这个系统中只有一个实例,并且这个实例是可以全局访问的。好比如封装了一个网络请求类 HttpUtil
,我们不想每次使用它的时候都 new
一个它的实例,这样请求一个网络就生成了一个它的对象,在内存和线程安全上来说是非常不理想的。我们期待的结果是所有网络请求共用一个它的实例,只需调用它的方法就可以请求网络,在整个系统中只存在一个 HttpUtil
的实例,这才是我们想要的结果。

下面我将一一介绍这五种常用的单例模式:

饿汉模式:

先回顾下Java的饿汉模式写法:

Java版:

public class Singleton {
//饿汉模式
private static Singleton mInstance = new Singleton();
​
public Singleton() {
}
​
public static Singleton getInstance() {
 return mInstance;
 }
}

饿汉模式的写法应该是最简单的,在类加载的时候就初始化了单例对象,然后在 getInstance()
静态方法中返回。接着我们看看Kotlin 的饿汉模式是什么样的。

Kotlin版:

object MySingleton{
 fun printMsg() = System.out.print("MySingleton")
}
// 调用
MySingleton.printMsg()

Kotlin的饿汉模式乍一看好像没代码,其实Kotlin中的 object
就声明了一个类为饿汉模式的单例,经过 object
修饰过得类就是一个静态类,默认实现了饿汉模式。

虽然饿汉模式在两种语言中都是很简单的写法,但是饿汉模式存在着一个缺点:类加载慢。

懒汉模式:线程不安全

Java版:

public class Singleton {
private static Singleton instance = null;
​
public Singleton() {
}
​
public static Singleton getInstance() {
 if (null == instance) {
   instance = new Singleton();
 }
 return instance;
}
}

Java的懒汉模式(线程不安全)版就是在类加载时候声明一个类对象,然后在静态方法 getInstance()
中判断一次是否为空,如果为空就 new
出它的实例,不为空直接返回这个对象。下面是Kotlin版的懒汉模式(线程不安全)版:

Kotlin版:

class MySingleton private constructor() {
 companion object {
   val mInstance by lazy(mode = LazyThreadSafetyMode.NONE) {
   MySingleton()
 }
 }
​
 fun printMsg() = System.out.print("MySingleton")
}
// 调用
MySingleton.mInstance.printMsg()

以上是kotlin默认懒汉写法,下面是自己手写和Java类似:

class MySingleton private constructor() {
​
 companion object {
 private var mInstance: MySingleton? = null
​
 fun getInstance(): MySingleton {
   if (mInstance == null) {
     mInstance = MySingleton()
   }
   return mInstance!!
 }
 }
​
 fun printMsg() = System.out.print("MySingleton")
}
// 调用
MySingleton.getInstance().printMsg()

可以看到Kotlin有两种写法,一种是Kotlin推荐的,一种是依照Java自己写的。Kotlin推荐采用 companion object
来声明一个静态变量或者静态方法,等同于Java的 static
,然后通过 by lazy
来声明一个单例。

为什么说这种写法是线程不安全的呢?我们仔细想想,在 getInstance()
方法中,只有一次判断是否为空,如果在 mInstance
未初始化的情况下,两次或者多次调用这个方法,那么都会通过 if (mInstance == null)
这个判断,造成两次或多次 new
MySingleton
对象,显然这个不满足我们心中的单例。

接着来看看改良版的懒汉模式,安全版:

懒汉模式:线程安全

Java版:

public class SingletonSecurity {
private static SingletonSecurity mInstance = null;
​
public SingletonSecurity() {
}
​
public static synchronized SingletonSecurity getInstance() {
 if (null == mInstance) {
   mInstance = new SingletonSecurity();
 }
 return mInstance;
}
}

Java版安全版是通过 synchronized
关键字给这个方法上锁,那么我们来看看Kotlin是否也是通过锁机制来实现的。

Kotlin版:

class MySingleton private constructor() {
​
 companion object {
 private var mInstance: MySingleton? = null
​
 @Synchronized
 fun getInstance(): MySingleton {
   if (mInstance == null) {
     mInstance = MySingleton()
   }
   return mInstance!!
 }
 }
​
 fun printMsg() = System.out.print("MySingleton")
}

没错,Kotlin也是采用了和Java一样的机制,通过 @Synchronized
来给 getInstance()
方法上锁,这就是两种语言的懒汉模式(安全版)。一次只能有一个线程进入该方法,其它线程要想进入该方法,不好意思,只能在外面排队等候,当前线程执行完该方法后,别的线程才能进入。

这种单例虽然保证了线程的安全,但是每次进入 getInstance()
方法的时候都需要进行同步,造成不必要的同步开销。

双重检测模式:

Java版:

public class SingletonSecurity {
 private volatile static SingletonSecurity mInstance = null;
​
 public SingletonSecurity() {
 }
​
 public static SingletonSecurity getmInstance() {
   if (null == mInstance) {
     synchronized (SingletonSecurity.class) {
       if (null == mInstance) {
         mInstance = new SingletonSecurity();
       }
    }
 }
 return mInstance;
 }
}

Java版的双重检测模式采用了两次为空判断,并且在声明变量的时候用到了 volatile
关键字,如果不了解 volatile
关键字的可以阅读望舒大大的 Java并发编程(三)volatile域
这篇文章,非常详细,很容易理解。

再来看看Kotlin是否也是如此:

Kotlin版:

class MySingleton private constructor() {
​
 companion object {
   val mInstance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
     MySingleton()
   }
 }
​
 fun printMsg() = System.out.print("MySingleton")
}

这种是Kotlin推荐的通过 by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED)
来创建一个双重检测的单例模式,和懒汉模式(线程不安全)版类似,只是 mode
不一样了,大家要注意区分。

类似Java写法

class MySingleton private constructor() {
​
 companion object {
 @Volatile
 private var mInstance: MySingleton? = null
​
 fun getInstance(): MySingleton {
   if (mInstance == null) {
     synchronized(MySingleton::class) {
       if (mInstance == null) {
         mInstance = MySingleton()
       }
     }
   }
   return mInstance!!
   }
 }
​
 fun printMsg() = System.out.print("MySingleton")
}

这是笔者依照Java版的写法实现的双重检测模式,写法和Java基本一致。

两次为空判断,第一次是为了不必要的同步,第二次是在 mInstance
为空的时候才创建实例。这种单例模式解决上面线程不安全、多与同步的问题,也是用的比较多的一种写法。

静态内部类单例模式:

Java版:

public class SingletonSecurity {
​
 public SingletonSecurity() {
 }
​
 public static SingletonSecurity getInstance() {
   return SingletonHolder.instance;
 }
​
 private static class SingletonHolder {
   private static final SingletonSecurity instance = new SingletonSecurity();
 }
}

这是Java的静态内部类单例写法,对这种写法放在Kotlin写法后面。

Kotlin版:

class MySingleton private constructor() {
​
 companion object {
   fun getInstance() = SingleHolder.mInstance
 }
​
 object SingleHolder {
   val mInstance: MySingleton = MySingleton()
 }
​
 fun printMsg() = System.out.print("MySingleton")
}

大家可以看到,在类加载的时候并没有声明 mInstance
变量,只有第一次调用 getInstance()
方法时,才会去 SingleHolder
类中取 mInstance
对象,在这个时候 SingleHolder
就会去创建一个 mInstance
实例,并且是静态的,只创建一次,全局共用。

这种方法是推荐替代双重检测模式的一种模式,在内存优化上、安全性上都得到了很好的保障。

这是笔者在这段时间中学习单例模式的一些心得,希望可以帮助到大家,如有错误,也望大家指正,我会在第一时间修正!!!

写在最后

每个人不是天生就强大,你若不努力,如何证明自己,加油!

Thank You!

–Taonce

Gorutins always execute & ldquo; Last in first out & rdquo;

上一篇

加速跑的京东云,打得一手“生态”好牌

下一篇

你也可能喜欢

单例模式之Java和Kotlin版

长按储存图像,分享给朋友