4
votes

I'm trying to convert some part of my project from java to kotlin. One of it is a singleton manager class. The java class looks like this

public class Manager {
  private static volatile Manager Instance = null;
  private static final Object InstanceLock = new Object();
  private Manager(Object1 object1, Object2 object2, Object3 object3){//...};
  public static boolean isInitialized(){
    synchronized(InstanceLock){
        return Instance == null;
    }
  }
  public static void initialize(Object1 object1, Object2 object2, Object3 object3){
      if(Instance == null){
         synchronized(InstanceLock){
             if(Instance == null){Instance = new Manager(object1, object2, object3};
         }
      }
  }
  public static getInstance(){
       Precondition.checkNotNull(Instance, msg...);
       return Instance;
  }
}

Also, I decompiled .kt back to java. In the companion class I get the following code.

public static final class Companion {
  @Nullable
  public final Manager getInstance() {
     return Manager.instance;
  }

  private final void setInstance(Manager var1) {
     Manager.instance = var1;
  }

  private final Object getInstanceLock() {
     return Manager.InstanceLock;
  }

  public final boolean isInitialized() {
     Object var1 = Manager.Companion.getInstanceLock();
     synchronized(var1){}

     boolean var4;
     try {
        var4 = Manager.Companion.getInstance() == null;
     } finally {
        ;
     }

     return var4;
  }

  public final void initialize(@NotNull String string1, @NotNull String string2) {
     Intrinsics.checkParameterIsNotNull(string1, "string1");
     Intrinsics.checkParameterIsNotNull(string2, "string2");
     if (((Manager.Companion)this).getInstance() == null) {
        Object var3 = ((Manager.Companion)this).getInstanceLock();
        synchronized(var3){}

        try {
           if (Manager.Companion.getInstance() == null) {
              Manager.Companion.setInstance(new Manager(string1, string2, (DefaultConstructorMarker)null));
           }

           Unit var5 = Unit.INSTANCE;
        } finally {
           ;
        }
     }

  }

  private Companion() {
  }

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

}

1) How do I achieve thread safety, singleton by using lateinit or lazy inside the kotlin companion object ? As I can see, the decompiled java code has a synchronized call in initialize function but nothing in the synchronize body.

2) I think kotlin object/lazy comes with thread safety guarantee, how do I take advantage of it in the double-checked locking pattern ?

3) Is there a better pattern than double-checked locking pattern? Assuming the constructor does need arguments.

4) Since I'm trying to make the impact of converting this manager class to kotlin file as small as possible (this Manager file is supposed to work with the rest of java code), what is the best approach ? I do notice I have to add @Jvmstatic or @Jvmfield in some other variables or functions inside of companion object so that I don't have to update other java file that has call to these static field in manager.

5) Additional question, what if this manager is now working in pure kotlin environment, what's the best practice of implementing a singleton class with multiple arguments ?

2

2 Answers

1
votes

The first answer does not address the synchronization, which, btw, is still an under appreciated complexity. There are still a ton of people running around saying simply do double-checked locking. But there are some pretty compelling arguments that show that DCL does not always work.

Interestingly, though, I had this same issue recently and found this article. While I did not like this the first time I found it, I revisited it a few times and warmed up to it, in large part because:

  • the author went and got code from the Kotlin stdlib
  • the result is a parameterized mechanism that while kind of ugly affords reuse, which is pretty compelling

Notice that the major issues are all broached in this treatment:

  • synchronization
  • complex initialization
  • parameterized initialization (crucial in Android, where the Context god object is ineradicable)
  • resulting compiled code

In short I think this is pretty much the first and last word on this topic, amazingly, found on Medium.

0
votes

I don't have answer to all of your questions, but there is a defined way to create a singleton class in Kotlin.

Instead of class prefix in front of the class name, use object.

For example,

object Manager {
    // your implementation
}

This make this class singleton and you can directly use this from Java like Manager.getInstance() (I didn't remeber the exact syntax but this should work) . Kotlin creates it for you.

You can check this for more reference.

Hope it will help you a little.