suma - Hilo java accediendo al objeto externo antes de ser creado
sincronizacion de hilos en java (5)
Sí, esta es una pregunta académica. Sé que la gente se quejará de que no estoy publicando ningún código, pero estoy realmente sorprendida con esta pregunta, realmente no sé por dónde empezar. Realmente agradecería una explicación y quizás algún ejemplo de código.
Si un constructor de objetos inicia un nuevo subproceso que ejecuta la ejecución del método de un objeto de clase interno anónimo, es posible que este nuevo subproceso pueda acceder al objeto exterior que lo rodea antes de que se haya construido completamente y sus campos se hayan inicializado por completo. ¿Cómo evitarías que esto suceda?
Cambiaría el título de la pregunta, ya que los hilos no se están accediendo a sí mismos, sino al segundo, al primero. Quiero decir: tienes un hilo, creando un objeto. Dentro del constructor para este objeto, declara una clase interna anónima que implementa Runnable. En el mismo constructor del primer subproceso, inicia un nuevo subproceso para ejecutar su clase interna anónima. Por lo tanto, estás teniendo dos hilos. Si quiere asegurarse de que el nuevo hilo no haga nada antes de que el constructor esté "completamente terminado", usaría algunos bloqueos en el constructor. De esta manera, el segundo hilo puede iniciarse, pero esperará hasta que el primer hilo termine.
public class A { int final number; A() { new Thread( new Runnable() { public void run() { System.out.pritnln("Number: " + number); } }).start(); number = 2; } }
Entonces, ¿una situación como esta?
public class MyClass {
private Object something;
public MyClass() {
new Thread() {
public void run() {
something = new Object();
}
}.start();
}
}
Dependiendo del código real utilizado, el comportamiento podría variar. Esta es la razón por la que los constructores deben elaborarse con cuidado para que, por ejemplo, no llamen a métodos no privados (una subclase podría anularla, permitiendo que se acceda a la superclase desde una subclase antes de que la superclase esté completamente inicializada). Aunque este ejemplo en particular trata con una sola clase y un subproceso, está relacionado con el problema de pérdida de referencia.
Esto se llama "fugas de esto". Aquí tienes el código
public class Test {
// this is guaranteed to be initialized after the constructor
private final int val;
public Test(int v) {
new Thread(new Runnable() {
@Override public void run() {
System.out.println("Val is " + val);
}
}).start();
this.val = v;
}
}
Adivina qué se imprimirá (puede ser, ya que es un hilo). Utilicé un campo final
para resaltar que se accede al objeto antes de que se haya inicializado completamente (los campos finales deben asignarse definitivamente después de la última línea de cada constructor)
Como te recuperas
No quieres pasar this
cuando estás en un constructor. Esto también significa que no desea llamar a métodos virtuales no finales en la misma clase (no estática, no privada), y no usar clases internas (las clases anónimas son clases internas), que están vinculadas implícitamente a la envolvente Por ejemplo, así es como podrían acceder a this
.
No estoy totalmente de acuerdo con la respuesta de Pablos porque depende en gran medida de su método de inicialización.
public class ThreadQuestion {
public volatile int number = 0;
public static void main(String[] args) {
ThreadQuestion q = new ThreadQuestion();
}
public ThreadQuestion() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(number);
}
});
try {
Thread.sleep(500);
} catch(Exception e) {
e.printStackTrace();
}
number = 1;
t.start();
}
}
Cuando tú
- Coloque
t.start()
al final, se imprimen los datos correctos. - Coloque
t.start()
antes del comando de reposo, se imprimirá 0 - elimine el comando de reposo y coloque
t.start()
antes de la asignación que puede imprimir 1 (no se puede determinar)
Juegue un juego mental en 3.) puede decir que una asignación "pequeña" de 1 tipo de datos simple funcionará como se esperaba, pero si crea una conexión de base de datos no logrará un resultado confiable.
No dudes en plantear cualquier duda.
Piense primero en la situación de un solo hilo:
Cada vez que crea un objeto a través de un objeto new
, se llama a su constructor, que (con suerte) inicializa los campos del nuevo objeto antes de que se devuelva una referencia a este objeto. Es decir, desde el punto de vista de la persona que llama, este new
es casi como una operación atómica:
Antes de llamar new
, no hay objeto. Después de regresar de new
, el objeto existe completamente inicializado.
Así que todo está bien.
La situación cambia ligeramente cuando entran varios hilos en juego. Pero tenemos que leer su cita con cuidado:
... ha sido completamente construido y sus campos completamente inicializados.
El punto crucial es fully
. La línea de asunto de su pregunta dice "antes de crear", pero lo que se entiende aquí no es antes de que se haya creado el objeto, sino entre la creación y la inicialización del objeto. En una situación de subprocesos múltiples, ya no se puede considerar (pseudo) atómico nuevo debido a esto (el tiempo fluye de izquierda a derecha):
Thread1 --> create object --> initialize object --> return from `new`
^
|
| (messing with the object)
Thread2 ------------------/
Entonces, ¿cómo puede Thread2 meterse con el objeto? Necesitaría una referencia a ese objeto, pero como new
solo devolverá el objeto después de que se haya creado e inicializado, esto debería ser imposible, ¿verdad?
Bueno, no, hay una forma en la que aún es posible, a saber, si el subproceso 2 se crea dentro del constructor del objeto . Entonces la situación sería así:
Thread1 --> create object --> create Thread2 --> initialize object --> return from `new`
| ^
| |
| | (messing with the object)
/-----/
Dado que Thread2 se crea después de que se haya creado el objeto (pero antes de que se haya inicializado completamente), ya hay una referencia al objeto del cual Thread2 podría obtener una retención. Una forma es simplemente si el constructor de Thread2 toma explícitamente una referencia al objeto como parámetro. Otra forma es mediante el uso de una clase interna no estática del objeto para el método de run
de Thread2.