2010-03-25 7 views
17

Goetz's Java Concurrency in Practice, pagina 41, indica come il riferimento this può sfuggire durante la costruzione. A "non farlo" esempio:Che cos'è un "oggetto costruito in modo incompleto"?

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

Qui this è "fuga" di via del fatto che doSomething(e) riferisce alla racchiude ThisEscape istanza. La situazione può essere risolta usando metodi factory statici (prima costruisci l'oggetto semplice, quindi registra il listener) invece dei costruttori pubblici (facendo tutto il lavoro). Il libro continua:

Pubblicare un oggetto dal suo costruttore può pubblicare un oggetto costruito in modo incompleto. Questo è vero anche se la pubblicazione è l'ultima istruzione nel costruttore. Se il riferimento this fuoriesce durante la costruzione, l'oggetto è considerato non correttamente costruito.

Non ho capito. Se la pubblicazione è l'ultima dichiarazione nel costruttore, non è stato fatto tutto il lavoro di costruzione prima di questo? Come mai è this non valido per allora? Apparentemente dopo c'è qualcosa di voodoo, ma cosa?

risposta

15

La fine di un costruttore è un posto speciale in termini di concorrenza, rispetto ai campi finali. Dal section 17.5 del linguaggio Java Specification:

Un oggetto è considerato completamente inizializzato quando le sue finiture costruttore. Un thread che può vedere solo un riferimento a un oggetto dopo che l'oggetto è stato completamente inizializzato è garantito per visualizzare i valori inizializzati correttamente per i campi finali dell'oggetto .

Il modello di utilizzo per i campi finali è uno semplice. Impostare i campi finali per un oggetto nel costruttore dell'oggetto. Non scrivere un riferimento all'oggetto che si sta costruendo in un punto dove un altro thread può vederlo prima che il costruttore dell'oggetto sia terminato. Se viene seguito, quindi quando l'oggetto è visto da un altro thread , quel thread vedrà sempre la versione correttamente costruita di campi finali dell'oggetto. Sarà anche vedere le versioni di qualsiasi oggetto o array a cui fanno riferimento tali campi finali che sono almeno aggiornati come i campi finali .

In altre parole, l'ascoltatore potrebbe visualizzare i campi finali con i valori predefiniti se esamina l'oggetto in un altro thread. Questo non succederebbe se la registrazione del listener avvenisse dopo che il costruttore ha completato.

In termini di ciò che accade, sospetto che ci sia una barriera di memoria implicita alla fine di un costruttore, assicurandosi che tutti i thread "vedano" i nuovi dati; senza quella barriera di memoria che è stata applicata, potrebbero esserci problemi.

+0

Wow, è sorprendente che i campi 'final', che di solito sono considerati compatibili con la concorrenza, sono il colpevole in questo caso! –

+0

@Joonas: Questo è il problema: sono compatibili con la concorrenza * se si assicura che il riferimento non esca dal costruttore *. Nella maggior parte dei casi è un prezzo piuttosto basso da pagare. –

+1

In realtà, questo si applica a tutti i campi, non solo quelli finali. –

2

C'è un tempo piccolo ma finito tra la fine di registerListener e la restituzione del costruttore. Un altro thread potrebbe utilizzare entrare in quel momento e tentare di chiamare doSomething(). Se il runtime non è tornato direttamente al tuo codice in quel momento, l'oggetto potrebbe trovarsi in uno stato non valido.

Non sono sicuro di java in realtà, ma un esempio a cui posso pensare è dove, eventualmente, il runtime trasferisce l'istanza prima di tornare a voi.

È una piccola occasione che ti concedo.

6

Un altro problema si pone quando si sottoclasse ThisEscape e la classe figlio richiama questo responsabile. L'implicito questo riferimento in EventListener avrebbe un oggetto costruito in modo incompleto.

+1

Buona chiamata. In particolare questo potrebbe creare problemi se la sottoclasse ignora i metodi virtuali da 'ThisEscape' - quei metodi sovrascritti potrebbero essere chiamati prima che lo stato di cui hanno bisogno sia stato impostato. –

+1

Questo è vero, ma fuori tema. –

+1

@Stephen C: Penso che questo sia un buon punto, sicuramente non fuori tema. –

Problemi correlati