java - thread - Permitiendo que esta referencia escape
thread java (4)
Significa llamar al código fuera de la clase y pasar
this
.
Ese código asumirá que la instancia está completamente inicializada, y puede romperse si no lo está.
De forma similar, su clase podría suponer que algunos métodos solo se invocarán después de que la instancia esté completamente inicializada, pero es probable que el código externo rompa esas suposiciones.final
métodosfinal
no pueden anularse, por lo que puede confiar en que no pasenthis
alto.
Si llama a un método nofinal
en el constructor para una clase nofinal
, una clase derivada puede anular ese método y pasarlo a cualquier parte.
Incluso cuando llame a los métodosfinal
, aún debe asegurarse de que estén escritos con seguridad, de que no pasenthis
ninguna parte, y que ellos mismos no llamen a ningún método que no seafinal
.
Agradecería ayuda para comprender lo siguiente de ''Java Concurrency in Practice'':
Llamar a un método de instancia anulable (uno que no es ni privado ni final) del constructor también puede permitir el escape de esta referencia.
- ¿''Escape'' aquí simplemente significa que probablemente estemos llamando a un método de instancia, antes de que la instancia esté completamente construida?
No veo ''esto'' escapando del alcance de la instancia de ninguna otra manera. - ¿Cómo evita ''final'' que esto ocurra? ¿Hay algún aspecto de ''final'' en la creación de instancias que me falta?
"Escape" significa que una referencia al objeto parcialmente construido se puede pasar a algún otro objeto en el sistema. Considera este escenario:
public Foo {
public Foo() {
setup();
}
protected void setup() {
// do stuff
}
}
public Bar extends Foo implements SomeListener {
@Override protected void setup() {
otherObject.addListener(this);
}
}
El problema es que el nuevo objeto Bar
se está registrando con otherObject
antes de que se complete su construcción. Ahora si otherObject
comienza a llamar a los métodos en barObject
, es posible que los campos no se hayan inicializado, o que barObject
esté en un estado incoherente. Una referencia al barObject
( this
en sí mismo) se ha "escapado" al resto del sistema antes de que esté listo.
En cambio, si el método setup()
es final
en Foo
, la clase Bar
no puede poner código allí que hará que el objeto sea visible antes de que termine el constructor Foo
.
Creo que el ejemplo es algo así como
public class Foo {
public Foo() {
doSomething();
}
public void doSomething() {
System.out.println("do something acceptable");
}
}
public class Bar extends Foo {
public void doSomething() {
System.out.println("yolo");
Zoom zoom = new Zoom(this); // at this point ''this'' might not be fully initialized
}
}
Debido a que el superconstructor siempre se llama primero (implícita o explícitamente), siempre se llamará a doSomething
para una clase secundaria. Como el método anterior no es ni final
ni private
, puede anularlo en una clase secundaria y hacer lo que quiera, lo que puede entrar en conflicto con lo que Foo#doSomething()
tenía que hacer.
Ejemplo de código BAD :
final class Publisher {
public static volatile Publisher published;
int num;
Publisher(int number) {
published = this;
// Initialization
this.num = number;
// ...
}
}
Si la inicialización de un objeto (y, en consecuencia, su construcción) depende de una comprobación de seguridad dentro del constructor, la verificación de seguridad puede anularse cuando un llamante que no es de confianza obtiene la instancia parcialmente inicializada. Ver la regla OBJ11-J . Tenga cuidado de no dejar que los constructores generen excepciones para obtener más información.
final class Publisher {
public static Publisher published;
int num;
Publisher(int number) {
// Initialization
this.num = number;
// ...
published = this;
}
}
Debido a que el campo no es volátil y no es final, el compilador puede reordenar las instrucciones dentro del constructor de tal manera que esta referencia se publique antes de que se hayan ejecutado las instrucciones de inicialización.
Código correcto :
final class Publisher {
static volatile Publisher published;
int num;
Publisher(int number) {
// Initialization
this.num = number;
// ...
published = this;
}
}
Se dice que esta referencia ha escapado cuando está disponible más allá de su alcance actual. Las siguientes son formas comunes por las cuales esta referencia puede escapar:
Returning this from a non-private, overridable method that is invoked from the constructor of a class whose object is being
construido. (Para obtener más información, consulte la regla MET05-J. Asegúrese de que los constructores no invoquen métodos invalidables). Esto se devuelve desde un método no privado de una clase mutable, que permite al llamador manipular el estado del objeto indirectamente. Esto ocurre comúnmente en implementaciones de encadenamiento de métodos; ver la regla VNA04-J. Asegúrese de que las llamadas a los métodos encadenados sean atómicas para obtener más información. Pasando esto como un argumento a un método alienígena invocado desde el constructor de una clase cuyo objeto se está construyendo. Usando clases internas. Una clase interna contiene implícitamente una referencia a la instancia de su clase externa a menos que la clase interna se declare estática. Publicación asignando esto a una variable pública estática del constructor de una clase cuyo objeto se está construyendo. Lanzando una excepción desde un constructor. Hacerlo puede hacer que el código sea vulnerable a un ataque de finalizador; ver la regla OBJ11-J. Tenga cuidado de no dejar que los constructores generen excepciones para obtener más información. Pasar el estado interno del objeto a un método alienígena. Esto permite que el método recupere esta referencia del objeto miembro interno.
Esta regla describe las posibles consecuencias de permitir que esta referencia escape durante la construcción del objeto, incluidas las condiciones de carrera y la inicialización incorrecta. Por ejemplo, declarar un campo final normalmente garantiza que todos los hilos vean el campo en un estado totalmente inicializado; sin embargo, permitir que esta referencia se escape durante la construcción del objeto puede exponer el campo a otros hilos en un estado no inicializado o parcialmente inicializado. Regla TSM03-J. No publique objetos parcialmente inicializados, que describen las garantías proporcionadas por diversos mecanismos para la publicación segura, se basa en el cumplimiento de esta regla. En consecuencia, los programas no deben permitir que esta referencia escape durante la construcción del objeto.
En general, es importante detectar casos en los que esta referencia puede filtrarse más allá del alcance del contexto actual. En particular, las variables y métodos públicos deben ser cuidadosamente analizados.