Sezione 3.2.1 di "Java Concurrency in Practice" di Goetz contiene la seguente regola:Java: sicuro "leak" questo riferimento nel costruttore per la classe finale tramite _happens-before_ relation?
Non lasciare che il riferimento
this
di fuoriuscire durante la costruzione
Capisco che, in generale, permettendo this
la fuga può portare ad altri thread che vedono versioni del tuo oggetto costruite in modo incompleto e violare la garanzia di sicurezza dell'inizializzazione dei campi final
(come ad esempio here)
Ma è mai possibile perdere in sicurezza this
? In particolare, se si stabilisce una relazione happen-before
prima della perdita?
Ad esempio, il official Executor Javadoc dice
azioni in un filo prima di presentare un oggetto
Runnable
a unExecutor
accade-prima sua esecuzione inizia, forse in un altro thread
My comprensione ingenua della lettura del modello di memoria Java questo è che qualcosa come il seguente dovrebbe essere sicuro, anche se perde this
prima della fine del costruttore:
public final class Foo {
private final String str1;
private String str2;
public Foo(Executor ex) {
str1 = "I'm final";
str2 = "I'm not";
ex.execute(new Runnable() {
// Oops: Leakage!
public void run() { System.out.println(str1 + str2);}
});
}
}
Cioè, anche se abbiamo fatto trapelare this
a un potenzialmente dannoso Executor
, le assegnazioni a str1
e str2
accadere, prima la perdita, per cui l'oggetto è (per tutti gli effetti) completamente costruiti, anche se non è stato "completamente inizializzato" per JLS 17.5.
Si noti che anch'io sto richiedendo che la classe sia final
, poiché i campi di ogni sottoclasse verrebbero inizializzati dopo la perdita.
Mi manca qualcosa qui? Questo è effettivamente garantito per essere ben educato? Mi sembra un esempio legittimo di "Piggybacking on synchronization" (16.1.4) In generale, apprezzerei molto ogni suggerimento su risorse aggiuntive in cui questi problemi sono trattati.
EDIT: Sono consapevole che, come osservato da @jtahlborn, posso evitare il problema utilizzando una fabbrica statica pubblica. Sto cercando una risposta alla domanda direttamente per consolidare la mia comprensione del modello di memoria Java.
EDIT # 2: This answer allude a quello che sto cercando di arrivare. Cioè, seguendo la regola del JLS ivi citata è sufficiente per garantire la visibilità di tutti i campi final
. Ma è necessario, oppure possiamo fare uso di altri meccanismi - prima del per garantire le nostre garanzie di visibilità?
in base al libro, la ragione è "un oggetto è in uno stato prevedibile e coerente solo dopo il costruttore _returns_" (enfasi il mio). la costruzione degli oggetti è garantita per essere completa solo quando il costruttore ritorna, quindi la relazione tra prima e l'evento è troppo presto (deve essere _dopo che il costruttore restituisce). – jtahlborn
Concordo con la vostra enfasi qui - le garanzie integrate circa, ad es. la semantica 'finale' richiede di tornare prima dal costruttore. Ma la mia domanda è quali proprietà specifiche vengono abbandonate non aspettando fino a quel momento? Una lettura è che la variabile automagica "le variabili finali sono garantite per essere inizializzate in modo sicuro" non è più garantita per te, ma puoi comunque affermare manualmente l'ordine tramite le barriere della memoria prima o esplicite (se tali cose esistono in Java). Un'altra lettura è, beh, demoni nasali. Sto cercando di trovare una spiegazione più precisa, – Tom
, l'accadere-prima sembrerebbe garantire che il normale compito dei membri sia "fatto". tuttavia, per me, la creazione di oggetti coinvolge una serie di altri processi "interni" (allocazione della memoria, gestione della gestione di jvm, mutex interno, ecc.). non è garantito che un oggetto sia completamente valido fino al ritorno del costruttore. onestamente, non sono davvero sicuro di cosa stai cercando qui. la specifica dice "non sei garantito" e ciò consente a jvm implementor di fare tutto ciò che vuole fino al completamento del costruttore. solo perché "potrebbe funzionare" su un dato jvm non significa nulla. – jtahlborn