为什么单例 Java 类会创建两个实例?

问题描述 投票:0回答:3

我已经在互联网上浏览了一些相关主题,例如this和这里的问题,例如thisthisthis,但我一无所获。这是我的简化代码:

主要活动

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();
  }
}

测试1

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() {}
}

测试2

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());
  }
}

结果:

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

为什么要创建类

Test1
的两个实例(
2a7bfa4
7e2a464
)?

请注意,

Test2
仅包含静态方法,它不会被实例化。

该应用程序在单个本机进程中运行,因此这些类应该由同一个类加载器加载(如果我理解正确的话)。

声明和初始化(在静态方法或静态初始化块内部或外部)保存其他类实例的最终静态变量是错误/不好的做法吗?还是在某些情况下是错误的?

java android class static-initialization
3个回答
3
投票

这似乎是一个非常有趣的问题。我已经运行了相同的项目,但直接从 public static void main 运行,它返回了预期的良好结果。没有损坏。working well

所以它一定与 android 以及如何在项目中加载类有关。


1
投票

这里没问题

我用纯 Java 而不是 Android 编写了类似的代码。我选择了吉利根岛的主题,这样比

Test1/2
更容易混淆。

似乎运行正常。

首先,单例类,

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 ( ) {}
}

第二个类是

Island
,持有对
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 );
    }
}

最后,一个运行该代码的应用程序,位于名为

Solo
的类中。

package work.basil.example;

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

运行时:

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

正如有人评论的那样,您可能会看到缓存内存可见性问题。如果是这样,可能需要用

volatile
标记你的单例变量。

如果不熟悉该关键字和此问题,并且您正在使用线程,请研究 Java 内存模型。反复阅读 Brian Goetz 等人撰写的 Java 并发实践

您可能会考虑以不同的方式实现您的单例。

多个类加载器

您的应用程序运行时是否涉及多个类加载器?如果是这样,请编辑您的问题以进行解释。请参阅问题,具有多个不同类加载器的单例类

使用
Enum
作为单例

我相信 Java 世界的共识是,使用 Java 中简单但功能强大的 enum 工具 是实现单例的最佳且最安全的方法。

package work.basil.example;

enum Gilligan
{
    INSTANCE;
}

要访问实例,请使用

Gilligan.INSTANCE
而不是调用 getter 方法。

并且无需在第二个类上存储对单例的静态引用。只要在代码中需要访问单例的地方使用

Gilligan.INSTANCE
即可。

警告:为单例使用枚举并不能解决运行时多个类加载器的问题。

请参阅:使用枚举实现单例(在 Java 中)

我将跳过有关使用单例有时会掩盖不良 OOP 设计并可能使测试变得困难的常见警告。


0
投票

包 com.test.staticvariables;

导入android.app.Activity; 导入 android.os.Bundle;

公共类 MainActivity 扩展 Activity {

@覆盖 protected void onCreate(Bundle savingInstanceState) {

//---> 在为您的活动创建视图之前初始化您的单例

Test1 test1 = Test1.getInstance();

super.onCreate(savedInstanceState); setContentView(R.layout.layout_of_activity);

//做某事 Test2.printTest1Instances();

} }

© www.soinside.com 2019 - 2024. All rights reserved.