![](https://i-blog.csdnimg.cn/blog_migrate/de940869dc89adad6a35bbb77cb0ec4f.png)
单例模式绝对是所有设计模式中最简单的模式,但是请相信我,我们许多人做错了。 因此,让我们进一步了解这种模式。
我们都知道使用Singleton模式的动机。 这种模式允许我们在应用程序的整个生命周期中只有一个对象实例。 为什么这很重要? 因为首先,当您只想要一个对象时,就不想创建多个资源消耗大的对象。 其次,有时,创建多个实例不仅成本高昂,而且可能使您进入不一致状态。 例如,您不希望有多个数据库对象,因为一个对象的更改可能会使其他对象不一致。
但是现在您可能在想,如果我只需要一个对象并且想从任何地方访问它,为什么不能将其设置为全局变量? 答案是,是的,您可以将其设为全局变量,但是如果该对象创建需要大量资源使用该怎么办? 全局变量可能仅在应用程序启动时创建。 您不希望Android应用程序有太多启动时间,对吗? 这些小东西在移动世界中很容易注意到。 为了避免这种情况,我们使用延迟加载,这意味着仅在需要时才创建对象。 一会儿我们将看到。
既然我们已经清楚了为什么要使用单例对象,让我们看看单例对象的特征是什么。
- 单例类不应允许其他类实例化它。 这意味着,没有任何一个类应该完全能够做
new Singleton()
。 我们如何防止这种情况? 这很简单。 只需将singleton类的构造函数设为private
。 这个简单的步骤确保了不会在任何地方实例化该对象……除了在同一类中之外(可以从同一类中调用私有构造函数-私有方法和字段也是如此)。 以下是Kotlin中带有私有构造函数的类的简单示例。
class Singleton private constructor ()
2.该类应该可以访问Singleton类:即使我们限制了Singleton类的实例化,我们仍然可以访问这种对象。 这是通过创建一个静态方法来返回Singleton对象。 在下面的示例中,我们制作了一个静态方法来检查实例是否为null。 如果是这样,它将实例化一个新对象并将其分配给INSTANCE
变量,该变量将在随后对getInstance
方法的调用中返回。
class Singleton private constructor (){
companion object {
private var INSTANCE : Singleton ? = null
fun getInstance(): Singleton{
if ( INSTANCE == null ){
INSTANCE = Singleton()
}
return INSTANCE !!
}
}
}
实现单例模式的大多数人都正确地做到了以上两点。 但是上述代码的问题在于,它可能会在多线程环境中产生您的Singleton类的多个实例。
fun getInstance(): Singleton{
if ( INSTANCE == null ){
// <--- Imagine Thread 2 is here
INSTANCE = Singleton() // <--- Imagine Thread 1 is here
}
...
在上面的代码中, 线程1和线程2都将产生两个不同的对象,这违反了单例类的目的。 这可能会对您的应用程序造成灾难性的影响,并且可能很难调试,因为与其他多线程问题一样,它们仅在某些情况下才会发生。
因此,简单而轻松的解决方案是仅在synchronized
锁内执行代码。 就像这样:
fun getInstance(): Singleton {
synchronized ( this ) {
if ( INSTANCE == null ){
INSTANCE = Singleton()
}
return INSTANCE !!
}
}
上面的代码将解决创建多个实例的情况,但可能会导致性能问题。 这意味着,即使在正确实例化了一个单例对象之后,对它的后续访问也将被同步,这是不必要的。 在多线程环境中读取对象没有问题。 一个好的停放指标是考虑同步代码块下的任何块都会使该块的执行速度降低100倍 。 如果您对此费用表示“满意”,而又不需要经常访问该单例对象,则可以在此处停止。 但是,如果确实需要多次访问它,则对它进行优化会有所帮助。
为了优化它,让我们考虑一下我们实际需要优化的东西。 我们只需要优化对象创建流程,而不需要优化对象流程的读取。
创建INSTANCE
变量后,同步流完全是不需要的开销。
一个简单的解决方法是比懒惰地更积极地创建对象。 看起来像这样:
class Singleton private constructor (){
companion object {
val INSTANCE = Singleton()
fun getInstance(): Singleton {
return INSTANCE
}
}
}
在上述情况下,JVM将确保创建后任何线程都可以访问INSTANCE
变量。 但是话又说回来,正如我们所讨论的,它甚至可以使您创建对象的启动时间加起来,甚至认为您以后需要或不再需要它。
因此,我们现在来看一下“双重检查锁定”,它可以帮助我们克服我们一直在谈论的所有上述问题。 在仔细检查锁定中,我们将首先查看是否创建了对象,如果未创建,则只有我们将应用同步。
companion object {
@Volatile private var INSTANCE : Singleton ? = null
fun getInstance(): Singleton {
if ( INSTANCE == null ){
synchronized ( this ) {
INSTANCE = Singleton()
}
}
return INSTANCE !!
}
}
如您所见,我们仅在对象实例化阶段应用同步。
您可能在想, @Volatile
注释在那里做什么。 与Java中的volatile
关键字相同。 如您所知,每个线程在其堆栈中都有其变量副本,并且在创建线程时,所有可访问的值都将复制到其堆栈中。 添加volatile
关键字就像是一个声明,上面说“此变量的值可能在其他线程中更改”一样。添加volatile
可确保刷新变量的值。 否则,可能会发生该线程永远不会更新其本地缓存的情况。
作为最后的重构,我想做一个小的改动,并利用Kotlin的语言结构。 我真的不喜欢尖叫!!
在return语句中。
companion object {
@Volatile private var INSTANCE : Singleton ? = null
fun getInstance(): Singleton {
return INSTANCE ?: synchronized ( this ) {
Singleton(). also {
INSTANCE = it
}
}
}
}
因此,有了它,可以成为单例的完美方法。 根据其他考虑因素,您的实现可能会有所不同,例如您的应用程序不在多线程环境中运行,或者您可以在整个方法中使用syncize关键字来降低性能。
感谢您阅读。 如果您觉得有趣,请随时拍一下(或十个?)并分享。 如果您想阅读更多类似的文章,可以关注我。
以下是您可能感兴趣的其他几篇文章:
- 2分钟内学习:Kotlin中的@JvmOverloads
- 每日Kotlin:静态方法
- RxJava — Flowables —什么,何时以及如何使用它?
- RxJava —计划程序—什么,何时以及如何使用它?
From: https://hackernoon.com/singleton-pattern-but-dont-get-too-comfortable-3ae69140097f