36

请阅读以下代码是“不安全构造”的示例,因为它允许此引用转义。我不太明白“这个”是如何逃脱的。我对java世界很陌生。任何人都可以帮助我理解这一点。

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}
4

4 回答 4

33

您在问题中发布的示例来自Brian Goetz 等人的“Java Concurrency In Practice” 。它在第 3.2 节“发布和转义”中。我不会尝试在此处重现该部分的详细信息。(去为你的书架买一本,或者从你的同事那里借一本!)

示例代码说明的问题是构造函数允许对正在构造的对象的引用在构造函数完成创建对象之前“转义”。这是一个问题,原因有两个:

  1. 如果引用转义,则某些对象可以在其构造函数完成初始化之前使用该对象,并看到它处于不一致(部分初始化)状态。即使对象在初始化完成后转义,声明子类也可能导致违反这一点。

  2. 根据JLS 17.5,对象的最终属性可以在不同步的情况下安全使用。但是,仅当对象引用在其构造函数完成之前未发布(不转义)时才适用。如果你打破这个规则,结果是一个潜在的并发错误,当代码在多核/多处理器机器上执行时可能会咬你。

这个ThisEscape例子是偷偷摸摸的,因为引用是this通过隐式传递给匿名EventListener类构造函数的引用转义的。但是,如果过早地明确发布参考文献,也会出现同样的问题。

下面举个例子来说明对象初始化不完全的问题:

public class Thing {
    public Thing (Leaker leaker) {
        leaker.leak(this);
    }
}

public class NamedThing  extends Thing {
    private String name;

    public NamedThing (Leaker leaker, String name) {
        super(leaker);

    }

    public String getName() {
        return name; 
    }
}

如果该Leaker.leak(...)方法调用getName()泄漏的对象,它将获得null... 因为此时对象的构造函数链尚未完成。

这是一个示例来说明final属性的不安全发布问题。

public class Unsafe {
    public final int foo = 42;
    public Unsafe(Unsafe[] leak) {
        leak[0] = this;   // Unsafe publication
        // Make the "window of vulnerability" large
        for (long l = 0; l < /* very large */ ; l++) {
            ...
        }
    }
}

public class Main {
    public static void main(String[] args) {
        final Unsafe[] leak = new Unsafe[1];
        new Thread(new Runnable() {
            public void run() {
                Thread.yield();   // (or sleep for a bit)
                new Unsafe(leak);
            }
        }).start();

        while (true) {
            if (leak[0] != null) {
                if (leak[0].foo == 42) {
                    System.err.println("OK");
                } else {
                    System.err.println("OUCH!");
                }
                System.exit(0);
            }
        }
    }
}

此应用程序的某些运行可能会打印“哎哟!” 而不是“OK”,表示由于通过数组Unsafe的不安全发布,主线程已经观察到对象处于“不可能”状态。leak这是否发生取决于您的 JVM 和硬件平台。

现在这个例子显然是人为的,但是不难想象这种事情在真正的多线程应用程序中怎么会发生。


作为 JSR 133 的结果,Java 5(JLS 第 3 版)中指定了当前的 Java 内存模型。在此之前,Java 与内存相关的方面未得到充分说明。引用早期版本/版本的来源已过时,但 Goetz 版本 1 中有关内存模型的信息是最新的。

内存模型的一些技术方面显然需要修改;请参阅https://openjdk.java.net/jeps/188https://www.infoq.com/articles/The-OpenJDK9-Revised-Java-Memory-Model/。然而,这项工作尚未出现在 JLS 修订版中。

于 2010-09-14T01:49:41.373 回答
14

我也有同样的疑问。

问题是在其他类中实例化的每个类都有对变量中封闭类的引用$this

这就是java所谓的综合,它不是你定义的东西,而是java自动为你做的事情。

如果您想亲自查看此内容,请在该doSomething(e)行中放置一个断点并检查具有哪些属性EventListener

于 2010-09-14T00:59:06.653 回答
8

我的猜测是该doSomething方法是在ThisEscape类中声明的,在这种情况下,引用肯定可以“逃脱”。
即,某些事件可以在其创建之后和构造函数EventListener执行完成之前立即触发。ThisEscape反过来,监听器将调用 的实例方法ThisEscape

我会稍微修改你的例子。现在变量var可以doSomething在构造函数中分配之前在方法中访问。

public class ThisEscape {
    private final int var;

    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            }
        );

        // more initialization
        // ...

        var = 10;
    }

    // result can be 0 or 10
    int doSomething(Event e) {
        return var;
    }
}
于 2010-09-14T00:55:29.313 回答
4

I just had the exact same question while reading "Java Concurrency In Practice" by Brian Goetz.

Stephen C's answer (the accepted one) is excellent! I only wanted to add on top of that one more resource I discovered. It is from JavaSpecialists, where Dr. Heinz M. Kabutz analyzes exactly the code example that devnull posted. He explains what classes are generated (outer, inner) after compiling and how this escapes. I found that explanation useful so I felt like sharing :)

issue192 (where he extends the example and provides a race condition.)

issue192b (where he explains what kind of classes are generated after compiling and how this escapes.)

于 2014-11-25T23:55:19.630 回答