带参数的 Kotlin 线程安全原生惰性单例
Kotlin thread safe native lazy singleton with parameter
在 java 中,我们可以使用双重检查锁定和 volatile 编写 thead 安全单例:
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开发者以后能做出非反射实现...
Object declaration 正是为了这个目的:
object Singleton {
//singleton members
}
它是惰性和线程安全的,它会在第一次调用时进行初始化,就像 Java 的静态初始化器一样。
您可以在顶层或在 class 或其他对象内声明 object
。
有关使用来自 Java 的 object
的更多信息,请参阅 。
至于参数,如果你想实现完全相同的语义(第一次调用 getInstance
接受它的参数来初始化单例,接下来调用只是 return 实例,丢弃参数),我建议这个构造:
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
。
另外,请参阅 关于为单例提供参数的信息。
Kotlin 与您的 Java 代码等效,但更安全。你的 double lock check is not recommended even for Java. In Java you should use an inner class on the static which is also explained in 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 中单例的混淆。并避免了这种模式的 "wrong versions" -- 其中有很多。相反,它提供了更简单、最安全的单例形式。
考虑到 lazy()
的使用,如果您有其他成员,每个人都会懒惰。由于它们是在传递给 lazy()
的 lambda 中初始化的,因此您可以执行您要求的有关自定义构造函数的操作,以及每个成员 属性.
因此,您延迟加载了 Singletons
对象( 首次访问实例 ),然后延迟加载了 something
(第一次访问成员时),对象构造完全灵活。
另请参阅:
附带说明, 查看 Kotlin 的对象注册表类型库,它类似于依赖注入,为您提供带有注入选项的单例:
我最近写了an article on that topic。
TL;DR 这是我想出的解决方案:
1) 创建一个SingletonHolder
class。你只需要写一次:
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)
}
单例初始化将是惰性和线程安全的。
在 java 中,我们可以使用双重检查锁定和 volatile 编写 thead 安全单例:
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开发者以后能做出非反射实现...
Object declaration 正是为了这个目的:
object Singleton {
//singleton members
}
它是惰性和线程安全的,它会在第一次调用时进行初始化,就像 Java 的静态初始化器一样。
您可以在顶层或在 class 或其他对象内声明 object
。
有关使用来自 Java 的 object
的更多信息,请参阅
至于参数,如果你想实现完全相同的语义(第一次调用
getInstance
接受它的参数来初始化单例,接下来调用只是 return 实例,丢弃参数),我建议这个构造:
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
。
另外,请参阅
Kotlin 与您的 Java 代码等效,但更安全。你的 double lock check is not recommended even for Java. In Java you should use an inner class on the static which is also explained in 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 中单例的混淆。并避免了这种模式的 "wrong versions" -- 其中有很多。相反,它提供了更简单、最安全的单例形式。
考虑到 lazy()
的使用,如果您有其他成员,每个人都会懒惰。由于它们是在传递给 lazy()
的 lambda 中初始化的,因此您可以执行您要求的有关自定义构造函数的操作,以及每个成员 属性.
因此,您延迟加载了 Singletons
对象( 首次访问实例 ),然后延迟加载了 something
(第一次访问成员时),对象构造完全灵活。
另请参阅:
附带说明, 查看 Kotlin 的对象注册表类型库,它类似于依赖注入,为您提供带有注入选项的单例:
我最近写了an article on that topic。 TL;DR 这是我想出的解决方案:
1) 创建一个SingletonHolder
class。你只需要写一次:
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)
}
单例初始化将是惰性和线程安全的。