在kotlin—对象文件中,介绍了kotlin创建的对象的几种方式,那么如何构建单例呢?
细心的朋友可能会发现简单的对象声明其实就是属于饿汉式单例的实现方式,伴生对象看似是单例实则不是,除非使用@JvmStatic对其内部成员说明。
那么kotlin中怎么实现单例呢?
1、饿汉式
object StaticA {
val x = 123
}
等价于java中的:
public class StaticA {
public static final StaticA sInstance = new StaticA()
private StaticA () {
}
}
优点:
- 实现简单
- 线程安全,因为其在类加载时就进行了初始化,虚拟机内部保证其线程安全,保证对常量/静态变量只进行一次初始化
缺点:
- 在类加载时就创建了静态对象,实际上可能不会用到,所以对资源来说是浪费了,同时时类的初始化变慢,性能上并不是很好
2、懒汉式
懒汉式就是懒加载,在使用时在进行初始化,其实现如下:
class SingleA {
companion object {
val sInstance by lazy(LazyThreadSafetyMode.NONE) {
SingleA()
}
}
}
等价于java的:
public class SingleA {
private static sInstance;
private SingleA() {
}
public static SingleA getInstance() {
if (sInstance == null) {
sInstance = new SingleA()
}
return sInstance;
}
}
优点:
- 延迟到使用时才进行初始化,提高了类加载的性能
缺点:
- 非线程安全,多个线程同时访问情况下,会创建多个实例
3、懒汉同步方法式
此方法是在第2中方式上在给房间加锁来实现,如下:
class SingleB {
companion object {
var sInstance: SingleB? = null
@Synchronized
fun getInstance(): SingleB? {
if (sInstance == null) {
sInstance = SingleB()
}
return sInstance
}
}
}
等价于java的:
public class SingleB {
private static SingleB sInstance;
private SingleB() {
}
public static synchronized SingleB getInstance() {
if (sInstance == null) {
sInstance = new SingleB();
}
return sInstance;
}
}
优点:
- 延迟到使用时才进行初始化,提高了类加载的性能
- 对方法使用了同步锁synchronized,保证了线程安全
缺点:
- synchronized应用在方法上,所有是对整个方法加了锁,所以性能上稍差
4、懒汉同步块式(推荐)
与第3中的方式差不多,不同的是同步锁应用在方法的内部语句块中:
class SingleC {
companion object {
val sIntance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
SingleC()
}
}
}
等价于java的:
public class SingleC {
private static SingleC sIntance;
private SingleC() {
}
public static SingleC getInstance() {
if (sIntance == null) {
synchronized (SingleC.class) {
if (sIntance == null) {
sIntance = new SingleC();
}
}
}
return sIntance;
}
}
优点:
- 延迟到使用时才进行初始化,提高了类加载的性能
- 在创建对象的语句块中使用了同步锁synchronized,保证线程安全的同时,降低了锁的作用范围
缺点:
- 需要1-2次的空判断
5、静态内部类式(推荐)
静态内部类的方式充分使用了语义的规则:
- 静态的语义规则——》使用到时才进行初始化,实现了懒加载
- 静态初始化是线程安全,线程的安全由虚拟机内部保证,保证静态初始化时只被初始化一次
以下是kotlin静态内部类的实现方式:
class SingleD {
companion object {
fun getInstance() = InstanceHelper.sSingle
}
object InstanceHelper {
val sSingle = SingleD()
}
}
等价于java的:
public class SingleD {
private SingleD() {
}
private static class InstanceHelper {
static SingleD sInstance = new SingleD();
}
public static SingleD getInstance() {
return InstanceHelper.sInstance;
}
}
优点:
- 延迟到使用时才进行初始化,提高了类加载的性能
- 使用静态初始化虚拟机保证线程安全的特性,实现了线程安全且锁的性能在虚拟机内部实现性能较好
缺点:
- 需要多一个额外的静态内部类来辅助实现
6、总结
综合上述的单例实现就数懒汉同步块式和静态内部类式的性能较好,那如何选择呢?
从其实现的不同方式不难发现:
- 懒汉同步块式使用1-2两次的判断,所以执行效率相比静态内部类式较差,可以理解为空间优先
- 静态内部类需要使用一个辅助类来实现,所以空间效率上比懒汉同步块式差,可以理解为空间换执行效率,执行效率优先
那我们该如何抉择呢?
如果空间上没有要求而执行效率上有要求,可以考虑使用静态内部类方式;如果空间上有要求而执行效率有要求,则考虑使用懒汉同步块式;如果空间和执行效率都有要求,需要权衡更需要那种方式而另一种则不得不做出牺牲,比较鱼和熊掌不可兼得;如果空间和执行效率都没有要求,就看个人喜好了。