Pregunta

Leer que el código siguiente es un ejemplo de "construcción insegura", ya que permite esta referencia a escapar. No podía conseguir cómo 'esto' se escapa. Soy bastante nuevo en el mundo Java. ¿Puede alguien ayudar a entender esto.

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}
¿Fue útil?

Solución

El ejemplo que has publicado en tu pregunta viene de "Java concurrencia en la práctica" por Brian Goetz et al. Es en la sección 3.2 "Publicación y escape". No voy a tratar de reproducir los detalles de esa sección aquí. (Ir a comprar una copia para su biblioteca, o pedir prestado una copia de sus compañeros de trabajo!)

El problema se ilustra por el código de ejemplo es que el constructor permite la referencia al objeto que está siendo construido para "escape" antes de que acabe constructor de crear el objeto. Este es un problema por dos razones:

  1. Si los escapes de referencia, algo puede utilizar el objeto antes de su constructor ha completado la inicialización y verlo en un inconsistente (en parte inicializado) estado. Incluso si el objeto se escapa después de la inicialización se ha completado, se declara una subclase puede hacer que esto sea violada.

  2. Según JLS 17,5 , atributos finales de un objeto pueden usarse con seguridad sin sincronización. Sin embargo, esto sólo es cierto si no está publicada la referencia a un objeto (no se escape) antes de terminado su constructor. Si se rompe esta regla, el resultado es un error de concurrencia insidioso que podría morder cuando el código se ejecuta en la maquina multi-core / multi-procesador.

El ejemplo ThisEscape es engañosa porque la referencia se escapa a través de la referencia this pasado implícitamente al constructor de la clase EventListener anónimo. Sin embargo, se producirán los mismos problemas si la referencia se publica de forma explícita antes de tiempo.

Este es un ejemplo para ilustrar el problema de los objetos de forma incompleta inicializados:

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

Si las llamadas a métodos Leaker.leak(...) getName() en el objeto filtrado, se pondrá null ... porque en ese momento la cadena de constructor del objeto no se ha completado.

Este es un ejemplo para ilustrar el problema publicación inseguro para los atributos 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);
            }
        }
    }
}

Algunas carreras de esta aplicación puede imprimir "¡Ay!" en lugar de "OK", lo que indica que el hilo principal ha observado el objeto Unsafe en un estado "imposible" debido a la publicación inseguro a través de la matriz leak. Que esto suceda o no dependerá de su JVM y la plataforma de hardware.

Ahora bien, este ejemplo es claramente artificial, pero no es difícil imaginar cómo este tipo de cosas pueden suceder en aplicaciones de subprocesos múltiples reales.


El actual modelo de memoria de Java se ha especificado en Java 5 (la 3ª edición de los JLS) como resultado de JSR 133. Antes de entonces, los aspectos relacionados con la memoria de Java están insuficientemente especificado. Las fuentes que se refieren a anteriores versiones / ediciones están fuera de fecha, pero la información sobre el modelo de memoria en la edición Goetz 1 es hasta la fecha.

Hay algunos aspectos técnicos del modelo de memoria que son aparentemente en necesidad de revisión; ver https://openjdk.java.net/jeps/188 y https://www.infoq.com/articles/The-OpenJDK9-Revised- java-memoria-Modelo / . Sin embargo, este trabajo todavía tiene que aparecer en una revisión JLS.

Otros consejos

Yo tenía exactamente la misma duda.

Lo que pasa es que cada clase que obtiene una instancia dentro de otra clase tiene una referencia a la clase envolvente en el $this variable.

Esto es lo que java llama un sintética , que no es algo que defina a estar allí, pero algo java hace de forma automática.

Si quieres ver esto por sí mismo poner un punto de interrupción en la línea doSomething(e) y comprobar lo que tiene propiedades EventListener.

Mi conjetura es que el método doSomething se declara en la clase ThisEscape, en el que caso de referencia sin duda puede 'escapar'.
Es decir, algún evento puede desencadenar este EventListener justo después de su creación y antes de la ejecución del constructor ThisEscape se ha completado. Y el oyente, a su vez, método de instancia de ThisEscape llamar.

Me va a modificar su ejemplo un poco. Ahora var variable se puede acceder en el método doSomething antes de que se le asigna en el constructor.

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

Yo sólo tenía exactamente la misma pregunta al leer " de Java concurrencia en la práctica " de Brian Goetz.

Stephen C 's respuesta (la aceptada) es excelente! Yo sólo quería añadir en la parte superior de un recurso más que descubrí. Es a partir de JavaSpecialists , donde el Dr. Heinz M. Kabutz analiza exactamente el ejemplo de código que devnull ha escrito. Explica lo que las clases se generan (externo, interno) después de compilar y cómo se escapa this. He encontrado que la explicación útil de forma que me sentí como compartir :)

issue192 (donde se extiende el ejemplo y proporciona una condición de carrera.)

issue192b (donde se explica qué tipo de clases se generan después de compilar y cómo escapes this.)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top