0
点赞
收藏
分享

微信扫一扫

kotlin单例模式与java对比

芝婵 2022-04-21 阅读 112
kotlin

一,饿汉式

1. java实现

public class SingletonDemo {

    private static SingletonDemo instance =  new SingletonDemo();	
    private SingletonDemo() {}

    public static SingletonDemo getInstance() {
        return instance;
    }
}

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

2. KT实现

object修饰的类,既是单例的实例,又是类名,又是对象

object SingletonDemo {
    fun show() = println("我是show函数...")
}

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

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

二,懒汉式

1. java实现

public class SingletonDemo {

    private static SingletonDemo instance;

    private SingletonDemo() {}

    public static SingletonDemo getInstance() {
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }

    public void show() {
        System.out.println("show");
    }

    public static void main(String[] args) {
        SingletonDemo.getInstance().show();
    }
}

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

2. KT实现

class SingletonDemo {

    companion object {

        private var instance : SingletonDemo? = null
            get() {
                if (field == null) {
                    field = SingletonDemo()
                }
                return field
            }

        fun getInstanceAction() = instance!!
    }

    fun show() {
        println("show")
    }
}

 Kotlin推荐采用companion object来声明一个静态变量或者静态方法,等同于Java的static。依赖 JVM 的类加载机制确保唯一和线程安全; JVM 加载类采用「按需加载」策略。

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

1. Java实现

在使用懒汉式会出现线程安全的问题,需要使用使用同步锁

public class SingletonDemo {

    private static SingletonDemo instance;

    private SingletonDemo() {}

    public static synchronized SingletonDemo  getInstance() {
        if (instance == null) {
            instance = new SingletonDemo();
        }
        return instance;
    }

    public void show() {
        System.out.println("show");
    }

    public static void main(String[] args) {
        SingletonDemo.getInstance().show();
    }
}

2. KT实现

在Kotlin中,如果你需要将方法声明为同步,需要添加 @Synchronized 注解。

class SingletonDemo {

    companion object {

        private var instance : SingletonDemo? = null
            get() {
                if (field == null) {
                    field = SingletonDemo()
                }
                return field
            }

        @Synchronized
        fun getInstanceAction() = instance!!
    }

    fun show() {
        println("show")
    }
}

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

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

1. Java版本

public class Singleton {

    private volatile static SingletonDemo instance; 
    private SingletonDemo() {}

    public static SingletonDemo getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo.class) {
                instance = new SingletonDemo();
            }
        }
        return instance;
    }	
    public void show() {
        System.out.println("show");
    }

    public static void main(String[] args) {
        SingletonDemo.getInstance().show();
    }
}

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

2. KT版本

class SingletonDemo private constructor() {	
    companion object {

        val instance : SingletonDemo by lazy (mode = LazyThreadSafetyMode.SYNCHRONIZED) { SingletonDemo() }
    }	
    fun show() {
        println("show")
    }
}

fun main() {
    SingletonDemo.instance.show()
}

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

3. KT的lazy属性代理内部实现源码分析

//expect关键字标记这个函数是平台相关,我们需要找到对应的actual关键字实现表示平台中一个相关实现 
public expect fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T>
//对应多平台中一个平台相关实现lazy函数
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
    //根据不同mode,返回不同的Lazy的实现,我们重点看下SynchronizedLazyImpl
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE//为了解决DCL带来指令重排序导致主存和工作内存数据不一致的问题,这里使用Volatile原语注解。具体Volatile为什么能解决这样的问题请接着看后面的分析
    private val lock = lock ?: this
    override val value: T
        get() {
    //当外部调用value值,get访问器会被调用
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
    //进行第一层的Check, 如果这个值已经初始化过了,直接返回_v1,避免走下面synchronized获取同步锁带来不必要资源开销。
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
    //进行第二层的Check,主要是为了_v2被初始化直接返回
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                //如果没有初始化执行initializer!!() lambda, 
                //实际上相当于执行外部调用传入的 by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { KLazilyDCLSingleton() } 中的KLazilyDCLSingleton()也即是返回KLazilyDCLSingleton实例对象
                    val typedValue  initializer!!()
                    _value = typedValue//并把这个实例对象保存在_value中
                    initializer = null
                    typedValue
                }
            }
        }
    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

Lazy是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。  

举报

相关推荐

0 条评论