多线程和静态代码块

4
我在我们的一个项目中遇到了一个奇怪的问题。我们使用JUnit运行单元测试,一段时间以来,我们开始并行运行测试以加速执行。大多数情况下,一切正常,但有时几乎所有测试都失败了。在下一次运行时,它们都通过了,而没有更改任何代码。
错误似乎表明,在多线程情况下,某些静态实例没有正确初始化或在初始化完成之前被使用。(我不能调试这个问题,因为当调试时,这个问题从未出现过 -> Heisenbug)。
抱歉,我无法提供一个最小的工作示例来显示错误,因为在尝试复制时它会消失。
具体问题是:当像下面这样声明变量时,当另一个线程调用foo()或bar()时,a或b的初始化是否有可能尚未完成?我认为静态块应该保证在调用任何方法之前执行。或者可能存在类加载器问题吗?或者JRE中已知的错误(我们目前卡在1.6.0_21上,我们的IT部门还没有提供更新版本)?
class C {
    private static final A a;
    private static final B b;

    static {
        a = new A(...);
        b = new B(...);
    }

    public static void foo() {
        useA();
    }

    public static void bar() {
        useB();
    }

}

我确定这与硬件无关,因为它在来自不同制造商的不同机器上都显示出来。测试正在使用服务器虚拟机。

谢谢,

Axel


new A()new B()会启动新线程吗?您尝试过隔离问题并创建能够复现问题的SSCCE吗? - assylias
据我所知,使用具有静态字段和方法的整个类是一种不好的做法,特别是在多线程情况下。为什么不创建非静态类并将其存储在静态字段中呢? - alaster
这就是在这里所做的。a和b是提供预先计算值的类的实例。这两个类都是不可变的,并且具有返回这些预先计算值的方法。计算是在各自的类构造函数中完成的。在访问时,最多只需检查参数、计算索引并返回包含先前计算值的数组元素。 - Axel
2个回答

0

这很可能是由于并发问题引起的,特别是如果您正在调用static的内容。尝试像这样同步您的线程:

class C {
    private static final A a;
    private static final B b;

    static {
        a = new A(...);
        b = new B(...);
    }

    public synchronized static void foo() {
        useA();
    }

    public synchronized static void bar() {
        useB();
    }

}

类A和B是不可变的,并经过彻底测试以确保线程安全。这里不应该需要使用同步。在我们的测试中,foo()和bar()被调用了数百万次,要么总是失败,要么从未失败,因此我认为这一定与初始化有关。 - Axel
静态字段只初始化一次(它们与类相关,而不是实例相关)。你没有指定在C中如何调用A和B实例,所以对我来说,在你的示例中它们是多余的。 - m0skit0
是的,只初始化一次。但在它们的初始化过程中似乎出了些问题。 - Axel
你试过我的解决方案了吗?如果你这么确定,那么问题肯定不在你展示的代码中。问题可能在A和B类的构造函数中。 - m0skit0
尝试解决问题的问题在于错误只会偶尔出现 - 也许一周一两次。但我现在认为我有线索了。我认为在路径的某个地方,我们有一个“损坏”的单例模式,它在构造函数中使用,即以非线程安全的方式实现的单例模式(这里有一篇有趣的文章:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)。 - Axel

0

如果AB在不同的线程上创建线程或执行代码,则可能会出现问题。无论如何,您确实希望静态内容是不可变的。

理论上可能会出现部分初始化的类,但如果存在循环依赖关系,则这种情况相当不太可能发生。


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