Java:在类初始化期间初始化同一类的静态字段

3

看起来在启动应用程序时发生了死锁。我有一个jstack文件,在其中我发现一个线程调用TariffModelManager.<clinit>方法,还有其他多个线程调用TariffModelManager.getInstance()方法。 TariffModelManager的代码如下:

public class TariffModelManager{
    ...
    private static final TariffModelManager tariffModelManager = TariffModelManager.getInstance();
    ...
    private static volatile TariffModelManager _instance;

    private TariffModelManager(){}

    public static TariffModelManager getInstance() {
        if(_instance == null) {
            synchronized(TariffModelManager.class) {
                if(_instance == null) _instance = new TariffModelManager();
            }
        }
        return _instance;
    }
    ... 
}

我认为,在getInstance()方法中调用已在代码下方描述的另一个字段(_instance),来初始化静态字段是一个明显的错误,但是实际发生了什么呢?
  1. 在类初始化期间,静态字段从上到下进行初始化;
  2. 我们试图初始化"tariffModelManager"字段......
  3. 调用getInstance()方法,检查_instance字段(但它尚未初始化!);
  4. 然后我们必须执行此类的构造函数;
  5. 如果_instance字段已在构造函数中初始化,则在静态字段初始化序列中可以将其设置为null吗?
我的问题不是如何解决这个问题,而是描述执行此代码时实际发生了什么! 更新:我在这里找到了详细的类初始化过程,但仍不清楚"tariffModelManager"和"_instance"字段的初始化顺序。
谢谢!

对我来说似乎可以工作。当我添加一个打印TariffModelManager.getInstance()的主方法时,它就会这样做。 - Robert
是的,我也测试过了,但不是在并发环境下。正如我上面所说,使用这段代码的应用在并发环境下存在问题,我认为这就是原因。 - Александр Мохов
1
  1. 在类初始化之前,没有人可以调用getInstance()方法,并且存在同步屏障以防止多个并发初始化。
  2. 静态字段按照你自己所说的顺序从前到后进行初始化。
- user207421
1个回答

1
你可以在这两个静态字段上设置字段断点来验证发生了什么。
我稍微修改了你的示例:
class TariffModelManager {
    private static final TariffModelManager tariffModelManager = TariffModelManager.getInstance();
    private static volatile TariffModelManager _instance = new TariffModelManager();

    private TariffModelManager() {
        System.out.println("From Constructor: " + this);
    }

    public static TariffModelManager getInstance() {
        if (_instance == null) {
            synchronized (TariffModelManager.class) {
                if (_instance == null) {
                    _instance = new TariffModelManager();
                    System.out.println("Creating new instance: " + _instance);
                }
            }
        }
        return _instance;
    }

    public static void main(String[] args) {
        TariffModelManager.getInstance();

        System.out.println("tariffModelManager: " + TariffModelManager.tariffModelManager);
        System.out.println("_instance: " + TariffModelManager._instance);
    }
}

这里是输出:

From Constructor: TariffModelManager@ea4a92b
Creating new instance: TariffModelManager@ea4a92b
From Constructor: TariffModelManager@3c5a99da
tariffModelManager: TariffModelManager@ea4a92b
_instance: TariffModelManager@3c5a99da

静态字段tariffModelManager会首先被初始化。当调用getInstance方法时,_instance为null,即默认值还未初始化。然后它会在该方法内部进行初始化,并将值分配给tariffModelManager。接下来,根据静态初始化顺序再次重新初始化:接下来,按照文本顺序执行类变量初始化器和静态初始化器,或接口的字段初始化器,就像它们是一个单独的块一样。

这很有趣,因为

class TariffModelManager {
    private static final TariffModelManager tariffModelManager = _instance;
    private static volatile TariffModelManager _instance = new TariffModelManager();

    private TariffModelManager() {
        System.out.println("From Constructor: " + this);
    }

    public static TariffModelManager getInstance() {
       return _instance;
    }
}

虽然Java中无效,但静态方法提供了一种“解决”它的方法(即tariffModelManager将为null,不是真正预期的结果,但仍然可以编译):

class TariffModelManager {
    private static final TariffModelManager tariffModelManager = getInstance();
    private static volatile TariffModelManager _instance = new TariffModelManager();

    private TariffModelManager() {
        System.out.println("From Constructor: " + this);
    }

    public static TariffModelManager getInstance() {
       return _instance;
    }
}

谢谢你的回答!我也测试了一下。很有趣,但是当我明确将_instance设置为null时,会创建两个不同的实例。但是如果我没有显式地分配它,即只留下:private static volatile TariffModelManager _instance; 那么"tariffModelManager"和"_instance"字段就是相同的! - Александр Мохов

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接