Domanda

Riferendosi al mio domanda precedente sugli oggetti costruiti in modo incompleto, ho una seconda domanda.Come ha sottolineato Jon Skeet, c'è una barriera di memoria implicita alla fine di un costruttore che lo garantisce final i campi sono visibili a tutti i thread.Ma cosa succede se un costruttore chiama un altro costruttore;c'è una tale barriera di memoria alla fine di ognuno di essi, o solo alla fine di quello che è stato chiamato per primo?Cioè, quando la soluzione "sbagliata" è:

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

E quella corretta sarebbe una versione del metodo di fabbrica:

public class SafeListener {
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        }
    }

    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

Funzionerebbe anche il seguente oppure no?

public class MyListener {
    private final EventListener listener;

    private MyListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        }
    }

    public MyListener(EventSource source) {
        this();
        source.register(listener);
    }
}

Aggiornamento: La domanda essenziale è che è così this() garantito in realtà chiamata il costruttore privato di cui sopra (nel qual caso ci sarebbe la barriera dove previsto e tutto sarebbe sicuro), oppure è possibile che il costruttore privato ottenga inline in quello pubblico come ottimizzazione per salvare una barriera di memoria (nel qual caso non ci sarebbe una barriera fino alla fine del costruttore pubblico)?

Sono le regole di this() definito precisamente da qualche parte?In caso contrario, penso che dobbiamo presumere che sia consentito l'inserimento di costruttori concatenati e probabilmente alcune JVM o forse anche javaclo stanno facendo.

È stato utile?

Soluzione

Penso che sia sicuro poiché il modello di memoria Java afferma che:

Permettere o essere un oggetto, e C essere un costruttore per o in cui un campo finale F è scritto.Un'azione di congelamento sul campo finale F Di o avviene quando C esce, normalmente o all'improvviso.Si noti che se un costruttore invoca un altro costruttore e il costruttore invocato imposta un campo finale, il congelamento per il campo finale avviene alla fine del costruttore invocato.

Altri suggerimenti

Un oggetto viene considerato completamente inizializzato quando il suo costruttore termina.

Questo vale anche per i costruttori concatenati.

Se devi registrarti nel costruttore, definisci il listener come una classe interna statica.Questo è sicuro.

La tua seconda versione non è corretta, perché consente al riferimento "questo" di sfuggire al processo di costruzione.Avere "questo" escape invalida le garanzie di sicurezza dell'inizializzazione che conferiscono sicurezza ai campi finali.

Per rispondere alla domanda implicita, la barriera alla fine della costruzione si verifica solo alla fine della costruzione dell'oggetto.L'intuizione offerta da un lettore sull'inlining è utile;dal punto di vista del Java Memory Model, i limiti del metodo non esistono.

MODIFICARE Dopo il commento che suggeriva al compilatore di incorporare il costruttore privato (non avevo pensato a quell'ottimizzazione) è probabile che il codice non sia sicuro.E la parte peggiore del codice multithread non sicuro è che sembra funzionare, quindi è meglio evitarlo completamente.Se vuoi giocare trucchi diversi (vuoi davvero evitare la factory per qualche motivo) considera l'aggiunta di un wrapper per garantire la coerenza dei dati nell'oggetto di implementazione interno e registrati nell'oggetto esterno.


La mia ipotesi è che sarà fragile ma ok.Il compilatore non può sapere se il costruttore interno verrà chiamato solo dall'interno di altri costruttori o meno, quindi deve assicurarsi che il risultato sia corretto per il codice che chiama solo il costruttore interno, quindi qualunque meccanismo utilizzi (barriera di memoria?) ha essere a posto lì.

Immagino che il compilatore aggiungerebbe la barriera di memoria alla fine di ogni singolo costruttore.Il problema è ancora lì:stai superando il this riferimento ad altro codice (possibilmente altri thread) prima che sia completamente costruito --questo è negativo--, ma se l'unica "costruzione" rimasta è la registrazione dell'ascoltatore, allora lo stato dell'oggetto è stabile come non lo sarà mai.

La soluzione è fragile in quanto qualche altro giorno, tu o qualche altro programmatore potreste dover aggiungere un altro membro all'oggetto e potreste dimenticare che i costruttori concatenati sono un trucco di concorrenza e potreste decidere di inizializzare il campo nel costruttore pubblico, e così facendo aggiungerai un difficile rilevare una potenziale corsa di dati nella tua applicazione, quindi proverei a evitare quel costrutto.

A proposito:La sicurezza ipotizzata potrebbe essere sbagliata.Non so quanto sia complesso/intelligente il compilatore e se la barriera di memoria (o simili) è qualcosa che potrebbe provare a ottimizzare...poiché il costruttore è privato, il compilatore dispone di informazioni sufficienti per sapere che viene chiamato solo da altri costruttori, e queste sono informazioni sufficienti per determinare che il meccanismo di sincronizzazione non è necessario nel costruttore interno...

L'escape del riferimento all'oggetto in c-tor può pubblicare un oggetto costruito in modo incompleto.Anche questo è vero se la pubblicazione è l'ultima istruzione nel costruttore.

Il tuo SafeListener potrebbe non comportarsi bene in un ambiente simultaneo, anche se viene eseguito l'inlining di c-tor (cosa che penso non sia: pensa a creare oggetti utilizzando la riflessione accedendo a c-tor privato).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top