Kotlin线程安全本机懒惰单身与参数

pun*_*sta 19 singleton multithreading kotlin

在java中,我们可以使用双重Checked Locking&volatile编写thead-safe单例:

    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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们怎么能用kotlin写呢?


关于对象

object A {
    object B {}
    object C {}
    init {
        C.hashCode()
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用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();
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

所有对象都在static块中调用构造函数.基于此,我们可以认为它不是懒惰的.

Сlose到正确的答案.

    class Singleton {
        companion object {
            val instance: Singleton by lazy(LazyThreadSafetyMode.PUBLICATION) { Singleton() }
        }
    }
Run Code Online (Sandbox Code Playgroud)

反编译:

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();
      }
   }
Run Code Online (Sandbox Code Playgroud)

我希望Kotlin开发人员将来能够实现非反思......

Jay*_*ard 24

Kotlin具有相当于您的Java代码,但更安全.你的双重锁定检查不建议甚至对Java.在Java中,您应该在static上使用内部类,这也在Initialization-on-demand holder惯用法中进行了解释.

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

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

    val somethingLazyButLessSo: OtherType = OtherType()
    val moreLazies: FancyType by lazy() { ... }
}
Run Code Online (Sandbox Code Playgroud)

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

// 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...
Run Code Online (Sandbox Code Playgroud)

Kotlin有意避免人们对Java中单身人士的困惑.并避免这种模式的"错误版本" - 其中有许多.相反,它提供了更简单,最安全的单身形式.

鉴于使用lazy(),如果你有其他成员,每个人都会懒得.因为它们是在传递给lazy()你的lambda中初始化的,所以你可以做一些关于自定义构造函数和每个成员属性的问题.

因此,您可以延迟加载Singletons对象(在第一次访问实例时),然后进行更长时间的加载something(在成员的第一次访问时),以及对象构造的完全灵活性.

也可以看看:

作为旁注,请查看Kotlin的对象注册表类型库,它们与依赖注入类似,为您提供带注入选项的单例:

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

hot*_*key 14

对象声明正是为此目的:

object Singleton {
    //singleton members
}
Run Code Online (Sandbox Code Playgroud)

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

您可以object在顶级或类或其他对象内声明.

有关使用objectJava 处理s的更多信息,请参阅此答案.


至于参数,如果你想要实现完全相同的语义(第一次调用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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

另外,请参阅有关向单例提供参数的类似问题.


Bla*_*der 6

我最近写了一篇关于该主题的文章。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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

2)在您的单例中使用它:

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

    companion object : SingletonHolder<MySingleton, ArgumentType>(::MySingleton)
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这只是通过在初始化完成后将创建者设置为null来释放一些内存的优化。我从lazy()的源代码中获取了它。 (2认同)