测试最终字段的初始化安全性

sma*_*sma 30 java multithreading final thread-safety jls

我试图简单地测试JLS保证的最终字段的初始化安全性.这是我写的一篇论文.但是,根据我当前的代码,我无法让它"失败".有人可以告诉我我做错了什么,或者这只是我必须反复运行然后看到一个不幸的时机失败?

这是我的代码:

public class TestClass {

    final int x;
    int y;
    static TestClass f;

    public TestClass() {
        x = 3;
        y = 4;
    }

    static void writer() {
        TestClass.f = new TestClass();
    }

    static void reader() {
        if (TestClass.f != null) {
            int i = TestClass.f.x; // guaranteed to see 3
            int j = TestClass.f.y; // could see 0

            System.out.println("i = " + i);
            System.out.println("j = " + j);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的线程正在调用它:

public class TestClient {

    public static void main(String[] args) {

        for (int i = 0; i < 10000; i++) {
            Thread writer = new Thread(new Runnable() {
                @Override
                public void run() {
                    TestClass.writer();
                }
            });

            writer.start();
        }

        for (int i = 0; i < 10000; i++) {
            Thread reader = new Thread(new Runnable() {
                @Override
                public void run() {
                    TestClass.reader();
                }
            });

            reader.start();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经多次运行这种情况了.我当前的循环产生10,000个线程,但我已经完成了1000,100000甚至一百万个.仍然没有失败.我总是看到两个值都是3和4.我怎么能让这个失败?

小智 18

我写了规范.TL; 这个答案的DR版本只是因为y 可能看到0,这并不意味着它保证 y 0.

在这种情况下,最终的字段规范保证您将看到3 for x,正如您所指出的那样.将编写器线程视为具有4条指令:

r1 = <create a new TestClass instance>
r1.x = 3;
r1.y = 4;
f = r1;
Run Code Online (Sandbox Code Playgroud)

您可能看不到3 for x的原因是编译器重新排序此代码:

r1 = <create a new TestClass instance>
f = r1;
r1.x = 3;
r1.y = 4;
Run Code Online (Sandbox Code Playgroud)

最终字段的保证通常在实践中实现的方式是确保构造函数在任何后续程序操作发生之前完成.想象一下,有人在r1.y = 4和f = r1之间竖起了一道大屏障.因此,在实践中,如果您有一个对象的任何最终字段,您可能会获得所有这些字段的可见性.

现在,从理论上讲,有人可以编写一个没有这种方式实现的编译器.事实上,许多人经常谈论通过编写可能最恶意的编译器来测试代码.这在C++人群中尤为常见,他们有很多很多未定义的语言角落,可能导致可怕的错误.


Pet*_*rey 6

从Java 5.0开始,您需要保证所有线程都能看到构造函数设置的最终状态.

如果你想看到这个失败,你可以尝试像1.3这样的旧JVM.

我不会打印出每个测试,我只打印出失败.你可能会在一百万人中失败但却错过了.但如果你只打印失败,它们应该很容易被发现.

查看此失败的更简单方法是添加到编写器.

f.y = 5;
Run Code Online (Sandbox Code Playgroud)

并测试

int y = TestClass.f.y; // could see 0, 4 or 5
if (y != 5)
    System.out.println("y = " + y);
Run Code Online (Sandbox Code Playgroud)

  • Java 5.0+ guarentees你总是会在另一个线程中看到y = 4,除非你在构造函数中启动线程. (2认同)
  • 阅读规范,我相信你是对的,它似乎只保证最后的领域,但是个人实现也可以免费保证非最终领域.AFAIK Oracle的Java 5.0+ JVM确保构造函数完成后所有线程都可以看到所有字段. (2认同)
  • 好的,我在上面的陈述中不正确.结果在构造函数完成后设置对f的引用.我通过在构造函数中不安全地发布引用来解决这个问题,例如:f = this.在这个例子中,我看到了两个变量的失败,这与不安全发布的语义一致.但是,我仍然无法在变量y上获得安全发布保证. (2认同)

ass*_*ias 5

我想查看失败的测试或解释为什么当前的JVM无法实现的测试。

多线程和测试

您不能通过测试来证明多线程应用程序已损坏(或没有损坏),原因有以下几种:

  • 该问题可能仅每运行x个小时就会出现一次,x如此之高,以至于您不太可能在短期测试中看到它
  • 该问题可能仅在JVM /处理器架构的某些组合中出现

在您的情况下,要使测试中断(即观察y == 0),将需要程序查看部分构造的对象,其中某些字段已正确构造,而有些则没有。这通常不会在x86 / hotspot上发生。

如何确定多线程代码是否损坏?

证明代码有效或损坏的唯一方法是对其应用JLS规则,然后查看结果是什么。使用数据竞争发布(围绕对象或y的发布不同步),JLS不保证y将被视为4(可以将其默认值为0看到)。

该代码真的可以破解吗?

实际上,某些JVM将使测试失败会更好。例如一些编译器(参见“测试案例显示,这是行不通的”,在这篇文章中)可以改变TestClass.f = new TestClass();成类似(因为是通过数据公布比赛):

(1) allocate memory
(2) write fields default values (x = 0; y = 0) //always first
(3) write final fields final values (x = 3)    //must happen before publication
(4) publish object                             //TestClass.f = new TestClass();
(5) write non final fields (y = 4)             //has been reodered after (4)
Run Code Online (Sandbox Code Playgroud)

JLS要求(2)和(3)在对象发布(4)之前发生。但是,由于数据争用,无法保证(5)-如果线程从未观察到该写操作,这实际上是合法的执行。通过适当的线程交织,因此可以想象,如果线程reader在4到5之间运行,您将获得所需的输出。

我手头没有Symantec JIT,因此无法通过实验证明:-)


归档时间:

查看次数:

1722 次

最近记录:

12 年,7 月 前