7

Voglio capire se volatile è necessario per pubblicare oggetti immutabili.Pubblicazione sicura di oggetti immutabili in Java

Ad esempio, supponendo abbiamo un oggetto immutabile A:

// class A is immutable 
class A { 
    final int field1; 
    final int field2; 

    public A(int f1, int f2) { 
    field1 = f1; 
    field2 = f2; 
    } 
} 

allora abbiamo una classe B cui si accede da diversi thread. Esso contiene un riferimento a un oggetto della classe A:

// class B publishes object of class A through a public filed 
class B { 
    private /* volatile? */ A toShare; 

    // this getter might be called from different threads 
    public A getA(){ 
    return toShare; 
    } 

    // this might be called from different threads 
    public void setA(num1, num2) { 
    toShare = new A(num1, num2); 
    } 
} 

Dalla mia lettura sembra oggetti immutabili possono essere pubblicati in modo sicuro attraverso qualunque mezzo , quindi vuol dire che non abbiamo bisogno di dichiarare toShare come volatile a garantire la sua visibilità della memoria?

+0

Se uno qualsiasi thread è in grado di recuperare un riferimento all'oggetto 'A' attraverso' toShare', che 'è garantito oggetto A' di essere pienamente inizializzato. – Savior

+0

Ma quello che ha recuperato 'A' è il valore più aggiornato? Poiché altri thread potrebbero aggiornare 'toShare' tramite il metodo' setA'. JVM garantisce che il valore aggiornato non verrà memorizzato nella cache localmente nel thread di impostazione? – aha

risposta

2

No, non è garantito che vedrete tutti gli aggiornamenti sul campo toShare dei dati condivisi. Questo perché i dati condivisi non utilizzano alcun costrutto di sincronizzazione che garantisca la sua visibilità o la visibilità dei riferimenti raggiungibili attraverso i thread. Questo rende aperto il gioco per numerose ottimizzazioni a livello di compilatore e hardware.

È possibile modificare in modo sicuro il campo toShare per fare riferimento a un String (che è anche immutabile per tutti i tuoi scopi) e probabilmente ti sentirai (e correttamente) più a disagio sulla sua visibilità di aggiornamento.

Here è possibile visualizzare un esempio rudimentale che ho creato in grado di mostrare come gli aggiornamenti vengono persi senza ulteriori misure per pubblicare le modifiche al riferimento di un oggetto immutabile. Ho ho fatto funzionare utilizzando il flag -server JVM su JDK 8u65 e Intel® Core ™ i5-2557M, trascurando il possibilmente gettato NullPointerException e ho visto i seguenti risultati:

  • Senza safe essere volatile, il secondo thread non lo fa interrompere perché non vede molte delle modifiche apportate dal primo thread
  • uscita

Console:

[T1] Shared data visible here is 2147483647 
  • Quando safe è cambiato per essere volatile, il secondo filo termina affiancato al primo filo
  • uscita

Console:

[T1] Shared data visible here is 2147483647 
[T2] Last read value here is 2147483646 
[T2] Shared data visible here is 2147483647 

P.S. E una domanda per te - cosa succede se sharedData (e non safe) è fatto volatile? Cosa potrebbe accadere secondo il JMM?

+0

Grazie per la spiegazione dettagliata! Per rendere volatile 'sharedData', la mia ipotesi iniziale sarebbe che il secondo thread non terminerebbe mai. La logica è che contrassegnando 'sharedData'' volatile' si renderebbe il thread che legge la sincronizzazione 'sharedData' alla vista memoria del thread che gli ha scritto per ultimo. Ma in questo esempio è scritto solo una volta durante la costruzione. Poiché il thread 1 non ha mai scritto in 'sharedData', il thread 2 non si sincronizzerebbe mai con la visualizzazione di memoria di thread 1. Tuttavia, quando l'ho provato, il thread 2 termina correttamente. Puoi approfondire il ragionamento che c'è dietro? molte grazie! – aha

+0

Non inserire il codice che è parte integrante della risposta su un sito esterno. Includi la tua risposta. – VGR

+0

@VGR Direi che il codice è un'illustrazione che non è parte integrante della mia risposta. L'ho messo principalmente per dimostrare che in molti casi (quando è necessario avere una quantificazione esistenziale o per rifiutare la quantificazione universale), un test rapido può essere sufficiente. Avrei comunque incluso il codice se non avessi pensato che avrebbe reso l'intero testo più difficile da leggere. Quindi mentre sono d'accordo con te in generale, trasmetterò il tuo suggerimento. –

0

risposta è NO , è necessario utilizzare volatile o in qualsiasi altro modo (ad esempio, aggiungere synchronized parola chiave per entrambe le firme ottenere e impostare) per fare un Happens/Prima bordo.campi semantici finali garantisce solo che se qualcuno vede un puntatore a un'istanza della classe, tutti i campi finali hanno i loro valori impostati in base al costruttore per il momento: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5

E questo non dice nulla circa la visibilità del riferimento stesso. Poiché il tuo esempio utilizza il campo non finale

privato A toShare;

si deve prendere cura di visibilità del campo con volatile o synchronized sezione o un java.util.concurrent.locks.Locks o AtomicReference ecc per avviare/garanzia di sincronizzazione della cache. Alcune cose utili, BTW, circa le finali e la pubblicazione di sicurezza http://shipilev.net/blog/2014/safe-public-construction/

http://shipilev.net/blog/2014/all-fields-are-final/