【问题标题】:Kotlin thread safe native lazy singleton with parameter带参数的 Kotlin 线程安全本机惰性单例
【发布时间】:2016-02-23 20:36:29
【问题描述】:

在 java 中,我们可以使用双重检查锁定和 volatile 编写安全的单例:

    public class Singleton {
        private static volatile Singleton instance;

        public static Singleton getInstance(String arg) {
        Singleton localInstance = instance;
        if (localInstance == null) {
            synchronized (Singleton.class) {
                localInstance = instance;
                if (localInstance == null) {
                    instance = localInstance = new Singleton(arg);
                }
            }
        }
        return localInstance;
    }
}

我们如何在 kotlin 中编写它?


关于对象

object A {
    object B {}
    object C {}
    init {
        C.hashCode()
    }
}

我使用 kotlin 反编译器来得到它

public final class A {
   public static final A INSTANCE;

   private A() {
      INSTANCE = (A)this;
      A.C.INSTANCE.hashCode();
   }
   static {
      new A();
   }

   public static final class B {
      public static final A.B INSTANCE;
      private B() {
         INSTANCE = (A.B)this;
      }
      static {
         new A.B();
      }
   }

   public static final class C {
      public static final A.C INSTANCE;
      private C() {
         INSTANCE = (A.C)this;
      }
      static {
         new A.C();
      }
   }
}

所有对象在static 块中都有构造函数调用。基于它,我们可以认为它并不偷懒。

С失去正确答案。

    class Singleton {
        companion object {
            val instance: Singleton by lazy(LazyThreadSafetyMode.PUBLICATION) { Singleton() }
        }
    }

反编译:

public static final class Companion {
      // $FF: synthetic field
      private static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Singleton.Companion.class), "instance", "getInstance()Lru/example/project/tech/Singleton;"))};

      @NotNull
      public final Singleton getInstance() {
         Lazy var1 = Singleton.instance$delegate;
         KProperty var3 = $$delegatedProperties[0];
         return (Singleton)var1.getValue();
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }

我希望 Kotlin 开发者在未来做出非反射实现...

【问题讨论】:

标签: multithreading singleton kotlin


【解决方案1】:

Kotlin 与您的 Java 代码等效,但更安全。不推荐您的double lock check即使对于 Java。在 Java 中,您应该使用 inner class on the static,这也在 Initialization-on-demand holder idiom 中进行了说明。

但那是 Java。 在 Kotlin 中,只需使用一个对象(以及可选的惰性委托):

object Singletons {
    val something: OfMyType by lazy() { ... }

    val somethingLazyButLessSo: OtherType = OtherType()
    val moreLazies: FancyType by lazy() { ... }
}

然后您可以访问任何成员变量:

// Singletons is lazy instantiated now, then something is lazy instantiated after.  
val thing = Singletons.something // This is Doubly Lazy!

// this one is already loaded due to previous line
val eager = Singletons.somethingLazyButLessSo

// and Singletons.moreLazies isn't loaded yet until first access...

Kotlin 有意避免人们对 Java 中的单例感到困惑。并避免这种模式的“错误版本”——其中有很多。相反,它提供了更简单和最安全的单例形式。

考虑到lazy() 的使用,如果您有其他成员,每个成员都会单独变得懒惰。由于它们是在传递给 lazy() 的 lambda 中初始化的,因此您可以执行您所要求的有关自定义构造函数和每个成员属性的事情。

因此,您会延迟加载 Singletons 对象(在首次访问实例时),然后延迟加载 something在首次访问成员时 >),以及对象构造的完全灵活性。

另见:

附带说明,查看类似于依赖注入的 Kotlin 对象注册表类型库,为您提供带有注入选项的单例:

【讨论】:

  • 实际上,lazy() 在内部使用带有 volatile 的双重检查锁定。此外,您不能将 init 参数传递给对象,因此确实需要替代解决方案。
  • 对象声明的初始化是线程安全的,并在第一次访问时完成。你不需要这个。只需使用 object 即可满足您的要求。 kotlinlang.org/docs/reference/…
【解决方案2】:

Object declaration 正是为此目的:

object Singleton {
    //singleton members
}

它是惰性且线程安全的,它在第一次调用时进行初始化,就像 Java 的静态初始化器一样。

您可以在顶层或在类或其他对象中声明object

有关在 Java 中使用 objects 的更多信息,请参阅 this answer


至于参数,如果你想实现完全相同的语义(第一次调用getInstance 使用它的参数来初始化单例,接下来的调用只是返回实例,删除参数),我建议这个结构:
private object SingletonInit { //invisible outside the file
    lateinit var arg0: String
}

object Singleton {
    val arg0: String = SingletonInit.arg0
}

fun Singleton(arg0: String): Singleton { //mimic a constructor, if you want
    synchronized(SingletonInit) {
        SingletonInit.arg0 = arg0
        return Singleton
    }
}

这个解决方案的主要缺陷是它需要在单独的文件中定义单例来隐藏object SingletonInit,并且在初始化之前不能直接引用Singleton

另外,请参阅 a similar question 关于向单例提供参数。

【讨论】:

  • 它不是懒惰的,对象也没有构造函数,所以它们没有动态设置的参数
  • @StasShakirov, objects 在引用它们的第一个程序语句之前被初始化,就像 Java 静态成员一样。那你说的“懒惰”是什么意思?至于论点,可以在这里找到一些东西:stackoverflow.com/questions/33128024/…(更新了答案)。
【解决方案3】:

我最近写了an article on that topic。 TL;DR 这是我想出的解决方案:

1) 创建一个SingletonHolder 类。你只需要写一次:

open class SingletonHolder<out T, in A>(creator: (A) -> T) {
    private var creator: ((A) -> T)? = creator
    @Volatile private var instance: T? = null

    fun getInstance(arg: A): T {
        val i = instance
        if (i != null) {
            return i
        }

        return synchronized(this) {
            val i2 = instance
            if (i2 != null) {
                i2
            } else {
                val created = creator!!(arg)
                instance = created
                creator = null
                created
            }
        }
    }
}

2) 在你的单身人士中像这样使用它:

class MySingleton private constructor(arg: ArgumentType) {
    init {
        // Init using argument
    }

    companion object : SingletonHolder<MySingleton, ArgumentType>(::MySingleton)
}

单例初始化将是惰性且线程安全的。

【讨论】:

  • 为什么不open class SingletonHolder&lt;out T, in A&gt;(private val creator: (A) -&gt; T)
  • 这只是一个优化,通过在初始化完成后将创建者设置为空来释放一些内存。我是从lazy()的源代码中取出来的。
猜你喜欢
  • 2010-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多