Kotlin thread save native lazy singleton с параметром

В java мы можем написать синглэты ad-save, используя двойную проверенную блокировку и нестабильность:

    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 в будущем сделают нереляционную реализацию...

Ответ 1

У Kotlin есть эквивалент вашего Java-кода, но более безопасный. Проверка двойной блокировки не рекомендуется даже для Java. В Java вы должны использовать внутренний класс на статике, который также объясняется в Инициализация по требованию Идиома владельца.

Но эта Java. В Котлине просто используйте объект (необязательно ленивый делегат):

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(), вы можете делать то, о чем вы просили, о настройке конструктора и для каждого свойства элемента.

В результате у вас есть ленивая загрузка объекта Singletons (при первом доступе экземпляра), а затем более лёгкая загрузка something (при первом доступе элемента) и полная гибкость в построении объекта.

См. также:

В качестве дополнительной заметки просмотрите библиотеки типов объектов для Kotlin, которые аналогичны инъекции зависимостей, предоставив вам варианты с вариантами впрыска:

  • Injekt - Я автор
  • Kodein - Очень похоже и хорошо

Ответ 2

Объявление объекта именно для этой цели:

object Singleton {
    //singleton members
}

Он ленив и потокобезопасен, он инициализируется при первом вызове, так же как и статические инициализаторы Java.

Вы можете объявить object на верхнем уровне или внутри класса или другого объекта.

Для получения дополнительной информации о работе с object с Java см. этот ответ.


Что касается параметра, если вы хотите получить точно такую ​​же семантику (первый вызов 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
    }
}

Основной недостаток этого решения состоит в том, что для определения singleton требуется отдельный файл, чтобы скрыть object SingletonInit, и вы не можете ссылаться на Singleton непосредственно до его инициализации.

Также см. аналогичный вопрос о предоставлении аргументов для одноэлемента.

Ответ 3

Недавно я написал статью по этому вопросу. 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)
}

Инициализация синглтона будет ленивой и потокобезопасной.