2010-03-25 11 views
17

Java Concurrency in Practice de Goetz, página 41, menciona cómo puede escaparse la referencia this durante la construcción. A "no hacer esto" ejemplo:¿Qué es un "objeto incompletamente construido"?

public class ThisEscape { 
    public ThisEscape(EventSource source) { 
     source.registerListener(
      new EventListener() { 
       public void onEvent(Event e) { 
        doSomething(e); 
       } 
      }); 
    } 
} 

Aquí this está "escapando" a través del hecho de que doSomething(e) se refiere a la ThisEscape ejemplo que encierra. La situación se puede solucionar utilizando métodos de fábrica estáticos (primero construye el objeto simple, luego registra al oyente) en lugar de los constructores públicos (haciendo todo el trabajo). El libro continúa:

La publicación de un objeto desde dentro de su constructor puede publicar un objeto incompletamente construido. Esto es verdadero incluso si la publicación es la última declaración en el constructor. Si se escapa la referencia this durante la construcción, el objeto se considera no construido correctamente.

No me sale esto. Si la publicación es la última declaración en el constructor, ¿no se ha realizado todo el trabajo de construcción antes de eso? ¿Cómo es que this no es válido para entonces? Aparentemente hay algo de vudú después de eso, ¿pero qué?

Respuesta

15

El final de un constructor es un lugar especial en términos de concurrencia, con respecto a los campos finales. De section 17.5 de la especificación del lenguaje Java:

Un objeto es considerado como inicializado por completo cuando sus acabados constructor. Un hilo que solo puede ver una referencia a un objeto después de que ese objeto ha sido completamente inicializado está garantizado para ver los valores correctamente inicializados para los campos finales del objeto .

El modelo de uso para los campos finales es un simple. Establezca los campos finales para un objeto en el constructor de ese objeto. No escriba una referencia al objeto que se está construyendo en un lugar donde otro subproceso puede verlo antes de que el constructor del objeto sea finalizado. Si esto se sigue, entonces cuando el objeto se ve por otra cadena , esa cadena siempre verá la versión correctamente construida de los campos finales de ese objeto. También verá las versiones de cualquier objeto o arreglo al que hacen referencia los campos finales que están al menos tan actualizados como los campos finales .

En otras palabras, su oyente podría terminar viendo los campos finales con sus valores predeterminados si examina el objeto en otro hilo. Esto no sucedería si el registro del oyente sucediera después de que el constructor se haya completado.

En términos de lo que está sucediendo, sospecho que hay una barrera de memoria implícita al final de un constructor, asegurándose de que todos los hilos "vean" los nuevos datos; sin que se haya aplicado esa barrera de memoria, podría haber problemas.

+0

¡Guau, es sorprendente que los campos 'finales', que generalmente se consideran favorables a la concurrencia, sean los culpables en este caso! –

+0

@Joonas: ese es el problema: son compatibles con la concurrencia * si se asegura de que la referencia no escapa del constructor *. En la mayoría de los casos, es un precio bastante bajo para pagar. –

+1

En realidad, esto se aplica a cualquier campo, no solo a los campos finales. –

2

Hay un tiempo pequeño pero finito entre el final de registerListener y el constructor que regresa. Otro hilo podría usar entrar en ese momento e intentar llamar a DoSomething(). Si el tiempo de ejecución no regresó directamente a su código en ese momento, el objeto podría estar en un estado no válido.

No estoy seguro de Java realmente, pero un ejemplo que puedo pensar es donde posiblemente el tiempo de ejecución reubique la instancia antes de volver a usted.

Es una pequeña oportunidad que te concedo.

6

Surge otro problema cuando subclase ThisEscape, y la clase child invoca este consructor. La referencia implícita en el EventListener tendría un objeto incompletamente construido.

+1

Buena llamada. En particular, esto podría crear problemas si la subclase anula los métodos virtuales de 'ThisEscape': los métodos reemplazados podrían invocarse antes de que se haya configurado el estado que requieren. –

+1

Esto es cierto, pero fuera del tema. –

+1

@Stephen C: Creo que este es un buen punto, definitivamente no fuera de tema. –

Cuestiones relacionadas