A sequence.

Singleton pattern is a common design pattern in our daily programming. A good singleton must be both unique and thread-safe. In Java, the singleton has been well documented, and the singleton pattern has become a programming paradigm.

Now that Google is pushing Kotlin, many people use Kotlin with a Java programming mindset and don’t take advantage of Kotlin’s features. If you were to write Kotlin’s singleton using Java programming ideas, it would feel strange.

In Kotlin, to implement the singleton, simply add the object keyword to the class. This is a thread-safe singleton, which is convenient.

There is a problem with this, however, because object Classes do not implement constructors, that is, we cannot pass parameters from the outside to initialize the singleton class.

This article will discuss the implementation of the singleton pattern under Kotlin, and how to elegantly construct a singleton pattern with parameters.

2. Kotlin’s singleton

2.1 Object Class singleton

The object keyword is the most commonly used method for constructing singletons under Kotlin, although it is not possible to pass parameters externally during construction. Let’s look at its properties first.

The object keyword is very simple to use and only needs to be applied directly to the class.

object SomeSingleton{
  fun sayHi(a){}}Copy the code

This is the simplest singleton pattern under Kotlin, if you want to have some initialization action, you can put it in an init block.

object SomeSingleton{
  init{
      // init
  }
  fun sayHi(a){}}Copy the code

Using methods is also very simple, and it is important to note that there are some differences between Kotlin calls and Java calls.

// Kotlin Language
SomeSingleton.sayHi()

// Java Language
SomeSingleton.INSTANCE.sayHi()
Copy the code

We know that Kotlin and Java work seamlessly together, and that the bytecode Kotlin eventually compiles can be turned into Java-like code.

Let’s take a look at how Kotlin’s object keyword works in Java. Through this transcoding analysis, we can easily understand the characteristics of Kotlin.

AS Tools → Kotlin → Show Kotlin Bytecode to view the Kotlin file’s Bytecode, and click the Decompile button to convert the Bytecode to Java code.

Kotlin’s object keyword has the following characteristics in Java:

  1. Classes use the final tag to indicate immutability.

  2. Internally declare a static final INSATNCE object of the current class.

  3. In a static code block, an INSTANCE object is initialized.

As you can see, in Kotlin’s Object, the class’s initialization lock is used to ensure thread-safety.

So what is an initialization lock for a class?

In simple terms, the JVM performs Class initialization during the Class initialization phase (that is, after the Class is loaded and before it is used by a thread), and during initialization, the JVM acquires a lock. This lock synchronizes the initialization of the same class by multiple threads, avoiding thread-safety issues when multi-threaded calls are made.

The figure above clearly shows the workflow of initializing locks for classes.

The object keyword in Kotlin, which uses the class initialization lock to ensure thread safety, can be safely used in scenarios where we do not need to pass external parameters for singleton initialization.

So one of the other things that you might worry about is that initializing a construct singleton when you load a class, isn’t that resource-unfriendly?

This is not a problem, because when a virtual machine runs an application, it doesn’t load all the classes at startup and initialize them, it’s a load on demand policy and only initialize them when it’s actually used.

For example: new Class, calling static methods, reflection, calling class.forname () methods, etc. You can do this with the singleton described in this article, which is to print the Log in the init block, and see when the Log is printed to verify this, but there is plenty of information to go into.

That is, the class is usually initialized by the virtual machine only if you actually use it. Of course, different virtual machines implement this differently, and this is not mandatory, but most follow this rule for performance.

2.2 Transferring singleton Parameters

A no-argument singleton can use the object keyword, but what if you want to initialize it with some external argument? Kotlin’s object cannot have any constructors, so it cannot pass any parameters.

There are also some application scenarios in Android, such as LocalBroadcastManager in Android, which is a singleton pattern with parameters.

LocalBroadcastManager. GetInstance (context). SendBroadcast (intent)Copy the code

How do you implement singletons with arguments in Java? Double Checked Locking + volatile is usually used to solve this problem.

public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton sInstance;
    private DoubleCheckSingleton(Context ctx) {
          // init
    }
    public static DoubleCheckSingleton getInstance(Context ctx) {
        if (sInstance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (sInstance == null) {
                    sInstance = newDoubleCheckSingleton(ctx); }}}returnsInstance; }}Copy the code

Volatile is used for visibility and to prevent reordering, so that parameters can be passed in while keeping the thread safe.

In Kotlin, however, the volatile keyword is not used. Instead, the @volatile annotation is used, and the singleton pattern is constructed with Kotlin’s associated objects.

Associated objects can simply call their methods using the class name as a qualifier, similar to static methods in Java.

final class SomeSingleton(context: Context) {
    private val mContext: Context = context
    companion object {
        @Volatile
        private var instance: SomeSingleton? = null
        fun getInstance(context: Context): SomeSingleton {
            val i = instance
            if (i ! =null) {
                return i
            }

            return synchronized(this) {
                val i2 = instance
                if (i2 ! =null) {
                    i2
                } else {
                    val created = SomeSingleton(context)
                    instance = created
                    created
                }
            }
        }
    }
}
Copy the code

This code borrows directly from Kotlin’s lazy(), which is implemented SynchronizedLazyImpl by default, as indicated by the class name, and uses Synchroinzed for thread safety.

In this way, you can implement a singleton pattern that can be constructed by passing parameters.

2.3 Encapsulating a Singleton with parameters

Support the singleton of passing parameters, we implemented. But it took 20+ lines of code to implement this singleton. It’s a hassle to copy this code every time you write a singleton, but you can encapsulate it for ease of use.

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 } } } }Copy the code

It can be easily encapsulated with an open class that supports inheritance and a generic. This encapsulation supports a constructor for a parameter, which can be extended or encapsulated as needed.

class SomeSingleton private constructor(context: Context) {
    init {
        // Init using context argument
        context.getString(R.string.app_name)
    }

    companion object : SingletonHolder<SomeSingleton, Context>(::SomeSingleton)
}
Copy the code

When you want to use a singleton after encapsulating it as a SingletonHolder class, the key code is done in one line.

2.4 use lazy

Lazy (), which is Kotlin’s standard delegate that accepts a lambda and returns an instance, was mentioned earlier in the introduction to parametric singletons.

If we want lazy initialization, we can use the lazy() proxy, which executes the lambda expression of lazy() and records the result on the first call to get(). Subsequent calls to get() only return the previously recorded result, which is perfect for lazy initialization scenarios.

class SomeSingleton{
    companion object {
        val instance: SomeSingleton by lazy { SomeSingleton() }
    }
}
Copy the code

Lazy () is thread-safe because it relies internally on synchronized by default.

But as I mentioned earlier, the class itself is loaded on demand, and the next step to calling it is definitely to use it too, so lazy() is not necessary as long as we use the singleton correctly.

Three. Summary moment

This article introduces some code tricks for implementing the singleton pattern under Kotlin, which I hope will help you. Finally, a quick summary.

  1. The no-argument singleton mode uses Kotlin’s Object, which relies on the class’s initialization lock for thread safety.

  2. The parameterized singleton pattern can be implemented using the double-checked lock + @volatile, or encapsulated as a SingletonHolder if you are too bothered.

  3. Lazy () delegates do allow lazy loading, but in a singleton scenario it’s not as convenient as using Object directly.