thread - Java: escape de referencia
thread java (4)
Supongo que el método ThisEscape
se declara en la clase ThisEscape
, en cuyo caso la referencia ciertamente puede ''escapar''.
Es decir, algún evento puede activar este EventListener
inmediatamente después de su creación y antes de que se ThisEscape
ejecución del constructor ThisEscape
. Y el oyente, a su vez, llamará al método de instancia de ThisEscape
.
Voy a modificar tu ejemplo un poco. Ahora se puede acceder a la variable var
en el método doSomething
antes de asignarla 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;
}
}
Lea que el siguiente código es un ejemplo de "construcción insegura", ya que permite escapar de esta referencia. No pude entender cómo ''esto'' se escapa. Soy bastante nuevo en el mundo de Java. ¿Alguien puede ayudarme a entender esto?
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
Yo tenía exactamente la misma duda.
El problema es que cada clase que se crea una instancia dentro de otra clase tiene una referencia a la clase adjunta en la variable $this
.
Esto es lo que java llama un sintético , no es algo que usted defina allí, sino algo que java hace por usted automáticamente.
Si quieres ver esto por ti mismo, coloca un punto de interrupción en la línea doSomething(e)
y comprueba qué propiedades tiene EventListener
.
El ejemplo que ha publicado en su pregunta proviene de "Java Concurrency In Practice" de Brian Goetz et al. Está en la sección 3.2 "Publicación y escape". No intentaré reproducir los detalles de esa sección aquí. (¡Ve a comprar una copia para tu estantería, o pide prestada una copia de tus compañeros de trabajo!)
El problema ilustrado por el código de ejemplo es que el constructor permite que la referencia al objeto que se está construyendo "escape" antes de que el constructor termine de crear el objeto. Este es un problema por dos razones:
Si la referencia escapa, algo puede usar el objeto antes de que su constructor haya completado la inicialización y lo vea en un estado inconsistente (parcialmente inicializado). Incluso si el objeto escapa después de que se ha completado la inicialización, declarar una subclase puede causar que se viole.
De acuerdo con JLS 17.5 , los atributos finales de un objeto se pueden usar de manera segura sin sincronización. Sin embargo, esto solo es cierto si la referencia del objeto no se publica (no escapa) antes de que termine su constructor. Si rompe esta regla, el resultado es un error insidioso de concurrencia que podría morderlo cuando el código se ejecuta en un equipo multi-core / multiprocesador.
El ejemplo de ThisEscape
es furtivo porque la referencia se escapa a través de this
referencia pasada implícitamente al constructor de la clase EventListener
anónimo. Sin embargo, surgirán los mismos problemas si la referencia se publica explícitamente demasiado pronto.
Aquí hay un ejemplo para ilustrar el problema de los objetos inicializados de forma incompleta:
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 el Leaker.leak(...)
llama a getName()
en el objeto filtrado, será null
... porque en ese momento la cadena de construcción del objeto no se ha completado.
Aquí hay un ejemplo para ilustrar el problema de la publicación insegura para final
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 ejecuciones de esta aplicación pueden imprimir "¡OUCH!" 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 insegura a través del conjunto de leak
. Si esto sucede o no dependerá de su JVM y su plataforma de hardware.
Ahora bien, este ejemplo es claramente artificial, pero no es difícil imaginar cómo puede suceder este tipo de cosas en aplicaciones reales de múltiples subprocesos.
Acabo de tener exactamente la misma pregunta mientras leía " Java Concurrency In Practice " por Brian Goetz.
¡La respuesta de Stephen C (la aceptada) es excelente! Solo quería agregarle ese recurso más que descubrí. Es de JavaSpecialists , donde el Dr. Heinz M. Kabutz analiza exactamente el ejemplo del código que Devnull publicó. Él explica qué clases se generan (externas, internas) después de compilar y cómo se escapa. Encontré esa explicación útil, así que me dieron ganas de compartir :)
issue192 (donde extiende el ejemplo y proporciona una condición de carrera).
issue192b (donde explica qué clase de clases se generan después de compilar y cómo se escapa).