Java:引用转义

2022-09-01 05:58:32

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

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

答案 1

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

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

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

  2. 根据 JLS 17.5,可以在没有同步的情况下安全地使用对象的最终属性。但是,仅当对象引用在其构造函数完成之前未发布(不转义)时,才存在这种情况。如果您违反此规则,则结果是一个阴险的并发错误,当在多核/多处理器计算机上执行代码时,它可能会咬您一口。

该示例是偷偷摸摸的,因为引用是通过隐式传递给匿名类构造函数的引用进行转义的。但是,如果参考文献过早明确发布,也会出现同样的问题。ThisEscapethisEventListener

下面是一个示例来说明对象初始化不完整的问题:

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

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

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


由于 JSR 133,当前的 Java 内存模型是在 Java 5(JLS 的第 3 版)中指定的。在此之前,Java的内存相关方面未被充分指定。引用早期版本/版本的源已过期,但Goetz版本1中有关内存模型的信息是最新的。

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


答案 2

我有完全相同的怀疑。

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

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

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


推荐