简体   繁体   中英

Why are there two instances created of a singleton Java class?

I have gone through some related topics on internet like this and questions here like this , this and this , but I'm getting nowhere. Here is my simplified code:

MainActivity :

package com.test.staticvariables;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Test1 test1 = Test1.getInstance();
    // do something
    Test2.printTest1Instances();
  }
}

Test1 :

package com.test.staticvariables;

public class Test1 {

  private static Test1 test1;

  static {
    System.out.println("Initializing Test1, loader: " + " " + Test1.class.getClassLoader());
  }

  static synchronized Test1 getInstance() {
    if (test1 == null) {
      test1 = new Test1();
    }
    return test1;
  }

  private Test1() {}
}

Test2 :

package com.test.staticvariables;

public class Test2 {

  private static final Test1 test1;
  // private static final Test1 test1 = Test1.getInstance();
  // private static final Test1 test1 = getTest1Instance();

  static {
    System.out.println("Initializing Test2, loader: " + " " + Test2.class.getClassLoader());
    test1 = Test1.getInstance();
  }

  static Test1 getTest1Instance() {
    return Test1.getInstance();
  }

  private Test2() {}

  static void printTest1Instances() {
    System.out.println("Test1 class variable: " + test1);
    System.out.println("Test1 instance variable: " + Test1.getInstance());
  }
}

Result:

Initializing Test1, loader: dalvik.system.PathClassLoader[DexPathList...]
Initializing Test2, loader: dalvik.system.PathClassLoader[DexPathList...]
Test1 class variable: com.test.staticvariables.Test1@2a7bfa4
Test1 instance variable: com.test.staticvariables.Test1@7e2a464

Why are two instances created of class Test1 ( 2a7bfa4 and 7e2a464 )?

Please note that Test2 only contains static methods, it's not being instantiated.

The app runs in a single native process, so the classes should be loaded by the same class loader (if I understand it correctly).

Declaring and initializing (inside or outside a static method or a static initialization block) a final static variable holding other class instance is a wrong / bad practice? Or is it wrong under certain situations?

It seems like very interesting question. I have run the same project but directly from public static void main and it returned the expected good results. Nothing broken. 运作良好

So it must have to do with android and how classes are loaded on your project.

No problem here

I wrote similar code in plain Java rather than Android. I chose a theme of Gilligan's Island to be less confusing than Test1/2 .

Seems to run properly.

First, the singleton class, Gilligan .

package work.basil.example;

import java.time.Instant;

public class Gilligan
{
    private static Gilligan gilligan;  // Hold a singleton.

    static
    {
        System.out.println( "Static class loading of Gilligan, loader: " + " " + Gilligan.class.getClassLoader() + " at " + Instant.now() );
    }

    static synchronized Gilligan getInstance ( )
    {
        // Lazy loading of our singleton, an instance of `Gilligan`.
        if ( gilligan == null )
        {
            gilligan = new Gilligan();
        }
        return gilligan;
    }

    // Constructor - private
    private Gilligan ( ) {}
}

The second class is Island , holding a static reference to the same singleton of Gilligan .

package work.basil.example;

import java.time.Instant;

public class Island
{
    private static final Gilligan gilligan;

    static
    {
        System.out.println( "Static class loading of Island, loader: " + " " + Island.class.getClassLoader() + " at " + Instant.now() );
        gilligan = Gilligan.getInstance();
    }

    // Constructor - private
    private Island ( ) {}

    static void proveSingleton ( )
    {

        boolean isSingleton = ( Island.gilligan == Gilligan.getInstance() );
        System.out.println( "Island.gilligan: " + gilligan );
        System.out.println( "Gilligan.gilligan: " + Gilligan.getInstance() );
        System.out.println( "Gilligan is a singleton: " + isSingleton );
    }
}

Lastly, an app to run that code, in a class named Solo .

package work.basil.example;

public class Solo
{
    public static void main ( String[] args )
    {
        Gilligan g = Gilligan.getInstance();
        Island.proveSingleton();
    }
}

When run:

Static class loading of Gilligan, loader:  jdk.internal.loader.ClassLoaders$AppClassLoader@73d16e93 at 2020-12-13T00:10:13.009691Z
Static class loading of Island, loader:  jdk.internal.loader.ClassLoaders$AppClassLoader@73d16e93 at 2020-12-13T00:10:13.028805Z
Island.gilligan: work.basil.example.Gilligan@4c873330
Gilligan.gilligan: work.basil.example.Gilligan@4c873330
Gilligan is a singleton: true

volatile

As someone commented, you may be seeing a cache memory visibility problem. If so, marking your singleton var with volatile might be called for.

If not familiar with that keyword and this issue, and you are using threads, study up on the Java Memory Model . Read and re-read Java Concurrency in Practice by Brian Goetz et al.

And you might consider implementing your singleton differently.

Multiple class loaders

Is your app running with multiple class loaders involved? If so, edit your Question to explain. And see the Question, Singleton class with several different classloaders .

Use Enum for singleton

I believe consensus in the Java world says that using the simple but powerful enum facility in Java is the best and safest way to implement a singleton.

package work.basil.example;

enum Gilligan
{
    INSTANCE;
}

To access the instance, use Gilligan.INSTANCE rather than calling a getter method.

And there is no need to store a static reference to the singleton on your second class . Simply use Gilligan.INSTANCE in your code wherever you need access to the singleton.

Caveat: Using an enum for your singleton does not solve the problem of multiple class loaders at runtime.

See: Implementing Singleton with an Enum (in Java)

I will skip the usual cautions about how use of a singleton is sometimes masking poor OOP design, and may make testing difficult.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM