Java:引用转义

dev*_*ull 24 java concurrent-programming

请阅读以下代码是"不安全构造"的示例,因为它允许此引用转义.我无法理解'这个'是如何逃脱的.我是java世界的新手.任何人都可以帮助我理解这一点.

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*n C 24

您在问题中发布的示例来自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; 
    }
}
Run Code Online (Sandbox Code Playgroud)

如果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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

此应用程序的某些运行可能会打印"OUCH!" 而不是"OK",表示Unsafe由于通过leak阵列的不安全发布,主线程已经观察到对象处于"不可能"状态.是否发生这种情况取决于您的JVM和硬件平台.

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


Pab*_*dez 13

我有同样的怀疑.

问题是,每一个被其他类中实例化类在变量封闭类的引用$this.

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

如果你想为自己看到这个,doSomething(e)在行中放一个断点并检查属性EventListener有哪些.

  • 我认为这是问题的一个重要部分; 它准确地解释了*`this`如何逃脱,这就是问题(*为什么*逃避是坏的不是问题的明确部分). (3认同)

Nik*_*bak 7

我的猜测是doSomethingThisEscape类中声明了方法,在这种情况下,引用肯定可以"逃避".
即,某些事件可以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;
    }
}
Run Code Online (Sandbox Code Playgroud)