2015-07-04 18 views
12

Attualmente sto leggendo JSR-133 (Java Memory Model) e non riesco a capire perché f.y potrebbe essere non inizializzato (potrebbe vedere 0). Qualcuno può spiegarmelo?Inizializzazione campo non finale

class FinalFieldExample { 
    final int x; 
    int y; 
    static FinalFieldExample f; 

    public FinalFieldExample() { 
     x = 3; 
     y = 4; 
    } 

    static void writer() { 
     f = new FinalFieldExample(); 
    } 

    static void reader() { 
     if (f != null) { 
      int i = f.x; // guaranteed to see 3 
      int j = f.y; // could see 0 
     } 
    } 
} 
+0

So che questo si riferisce alla JVM che è autorizzata a riordinare le scritture, ma non conosco i dettagli. : - | –

+0

Siccome penso che non abbiate letto correttamente jsr133, ciò è chiaramente menzionato. Non scrivere un riferimento all'oggetto che si sta costruendo in un punto in cui un altro thread può vederlo prima che il costruttore dell'oggetto sia finito. Se questo è seguito, quando l'oggetto viene visto da un altro thread, quel thread vedrà sempre la versione correttamente costruita di i campi finali dell'oggetto, ma per i thread non finali il thread può vedere il valore predefinito. – Prashant

+0

sì, questo è folle. speriamo che lo risolvano in java9. – ZhongYu

risposta

-1

modello di memoria di Java consente un thread di creare un FinalFieldExample, inizializzare finale x e salva un riferimento a un'istanza FinalFieldExample a f campo prima inizializzazione non finale y.

+0

Cosa succede se il campo 'x' è stato inizializzato dopo' y' nel costruttore? –

+0

È consentito riordinare le istruzioni, questo è anche il motivo per cui Java può pubblicare istanze non avviate –

+0

Questa risposta non è utile senza citazioni, citazioni pertinenti e ulteriori spiegazioni. Non ci sono informazioni nella risposta che non siano nella domanda. –

7

Questo è chiamato effetto "pubblicazione prematura".

Semplicissimo, JVM può riordinare le istruzioni del programma (per motivi di prestazioni), se tale riordino non viola le restrizioni di JMM.

Si vede che il codice f = new FinalFieldExample(); a funzionare come:

1. istanziare FinalFieldExample
2. assegnare 3 x
3. assegnare 4 a y
4. assegnare oggetto creato alla variabile f

Ma in codice fornito, niente può fermare JVM dall'istruzione riordino, in modo che possa eseguire il codice come:

1. istanziare FinalFieldExample
2. assegnare 3 x
3. assegnare crudo, oggetto non inizializzato completamente alla variabile f
4. assegnare 4 a y

Se riordino avviene in un'unica thread environment, non lo noteremo nemmeno. Questo perché ci aspettiamo che gli oggetti vengano completamente creati prima di iniziare a lavorare con loro e JVM rispetti le nostre aspettative. Ora, cosa può succedere se diversi thread eseguono questo codice contemporaneamente? Nel seguente esempio Filettatura1 è metodo writer() e Thread2 eseguendo - metodo reader():

filettatura 1: creare l'istanza di FinalFieldExample
filettatura 1: assegnare 3 x
filettatura 1: assegnare crudo, oggetto non inizializzato completamente alla variabile f
filettatura 2: lettura f, non è nullo
filettatura 2: lettura fx, è 3
filettatura 2: lettura fy, è ancora 0
filettatura 1: assegnare 4 a y

Definitivamente non buono. Per evitare che JVM faccia ciò, dobbiamo dargli ulteriori informazioni sul programma. Per questo particolare esempio, ci sono alcuni modi per risolvere la consistenza memoria:

  • dichiarare y come final variabile. Ciò causerà l'effetto "freeze". In breve, le variabili finali saranno sempre inizializzate nel momento in cui le si accede, se il riferimento all'oggetto non è trapelato durante la costruzione.
  • dichiarare f come volatile variabile.Questo creerà "synchronization order" e risolverà il problema. In breve, le istruzioni non possono essere riordinate sotto la scrittura volatile e sopra la lettura volatile. L'assegnazione alla variabile f è una scrittura volatile, ovvero le istruzioni new FinalFieldExample() non possono essere riordinate ed eseguite dopo l'assegnazione. La lettura dalla variabile f è una lettura volatile, pertanto la lettura di f.x non può essere eseguita prima di essa. La combinazione di v-write e v-read è chiamata ordine di sincronizzazione e fornisce la coerenza di memoria desiderata.

Here è un buon blog, in grado di rispondere a tutte le vostre domande su JMM.

+0

Se la chiamata al costruttore può avvenire dopo che il riferimento all'oggetto è stato pubblicato su 'f', in che modo una JVM soddisfa _garantito per vedere 3_? –

+0

@SotiriosDelimanolis questo è chiamato "freeze action". In breve, se c'è un assegnamento finale di un campo all'interno di un costruttore, JMM è costretto a eseguirlo prima che il riferimento al nuovo oggetto venga pubblicato. – AdamSkywalker

+0

Se avessimo il 'y = 4' prima dell'assegnazione al campo' final' 'x', questa azione di congelamento garantirà che' y' è anche inizializzato? Oppure una JVM può riordinarlo in seguito all'assegnazione di 'x' e pubblicare l'oggetto, lasciando' y' potenzialmente non inizializzato? –

1

La JVM può riordinare le letture e le scritture di memoria, pertanto il riferimento a f può essere scritto nella memoria principale prima del valore di f.y. Se un altro thread legge f.y tra queste due scritture, leggerà 0. Tuttavia, se si crea una barriera di memoria scrivendo un campo final o volatile, si legge e scrive dopo che la barriera non può essere riordinata rispetto a letture e scritture prima della barriera. Quindi è garantito che sia f sia f.y verranno scritti prima che un altro thread legga f.

Ho chiesto una domanda simile here. Le risposte vanno molto più in dettaglio.

Problemi correlati