Domanda

Read che il codice seguente è un esempio di "costruzione non sicuro" in quanto permette questo riferimento alla fuga. Io non riuscivo a ottenere how 'questo' fughe. Sono abbastanza nuovo per il mondo Java. Può uno aiutarmi a capire questo.

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

Soluzione

L'esempio a cui hai risposto alla tua domanda viene da "Java Concurrency in Practice" di Brian Goetz et al. E 'nella sezione 3.2 "Pubblicazione e fuga". Non tenterò di riprodurre i dettagli di quella sezione qui. (Andare a comprare una copia per il vostro scaffale, o prendere in prestito una copia dai vostri colleghi di lavoro!)

Il problema illustrato dal codice esempio è che il costruttore permette il riferimento all'oggetto essendo costruito a "fuga" prima del termine del costruttore creando l'oggetto. Questo è un problema per due motivi:

  1. La fuoriuscita di riferimento, qualcosa possono utilizzare l'oggetto prima della sua costruzione ha completato l'inizializzazione e vederlo in un inconsistente (parzialmente inizializzato) Stato. Anche se l'oggetto fuoriesce dopo l'inizializzazione è completata, dichiara una sottoclasse può causare questo essere violata.

  2. Secondo JLS 17,5 , attributi finali di un oggetto può essere utilizzata in modo sicuro senza sincronizzazione. Tuttavia, questo è vero solo se il riferimento oggetto non viene pubblicata (non sfugge) prima della sua costruzione finita. Se si interrompe questa regola, il risultato è un bug di concorrenza insidioso che potrebbe mordere quando il codice viene eseguito su una macchina multi-core / multi-processore.

L'esempio ThisEscape è subdolo perché il riferimento fuoriesce attraverso il riferimento di this passato implicitamente al costruttore della classe EventListener anonima. Tuttavia, sorgeranno gli stessi problemi se il riferimento è esplicitamente pubblicato troppo presto.

Ecco un esempio per illustrare il problema degli oggetti in modo incompleto inizializzati:

public class Thing {
    public Thing (Leaker leaker) {
        leaker.leak(this);
    }
}

public class NamedThing  extends Thing {
    private String name;

    public NamedThing (Leaker leaker, String name) {
        super(leaker);

    }

    public String getName() {
        return name; 
    }
}

Se le chiamate di metodo Leaker.leak(...) getName() sull'oggetto trapelato, otterrà null ... perché in quel momento la catena costruttore dell'oggetto non ha completato.

Ecco un esempio per illustrare il problema pubblicazione non sicuro per gli attributi final.

public class Unsafe {
    public final int foo = 42;
    public Unsafe(Unsafe[] leak) {
        leak[0] = this;   // Unsafe publication
        // Make the "window of vulnerability" large
        for (long l = 0; l < /* very large */ ; l++) {
            ...
        }
    }
}

public class Main {
    public static void main(String[] args) {
        final Unsafe[] leak = new Unsafe[1];
        new Thread(new Runnable() {
            public void run() {
                Thread.yield();   // (or sleep for a bit)
                new Unsafe(leak);
            }
        }).start();

        while (true) {
            if (leak[0] != null) {
                if (leak[0].foo == 42) {
                    System.err.println("OK");
                } else {
                    System.err.println("OUCH!");
                }
                System.exit(0);
            }
        }
    }
}

Alcuni percorsi di questa applicazione possono stampare "OUCH!" invece di "OK", indicando che il filo principale ha osservato l'oggetto Unsafe in uno stato "impossibile" a causa di pubblicazione pericoloso tramite la matrice leak. Se questo accade o no dipenderà dalla vostra JVM e la piattaforma hardware.

Ora, questo esempio è chiaramente artificiale, ma non è difficile immaginare come questo tipo di cosa può accadere in vere e proprie applicazioni multi-threaded.


L'attuale Java Memory Model è stato specificato in Java 5 (3 ° edizione dei JLS) a seguito di JSR 133. Prima di allora, sono stati specificati sotto-aspetti relativo alla memoria di Java. Le fonti che fanno riferimento a precedenti versioni / edizioni non sono aggiornati, ma le informazioni sul modello di memoria in Goetz edizione 1 siano aggiornati.

Ci sono alcuni aspetti tecnici del modello di memoria che sono a quanto pare ha bisogno di una revisione; vedi https://openjdk.java.net/jeps/188 e https://www.infoq.com/articles/The-OpenJDK9-Revised- Java-memoria-Model / . Tuttavia, questo lavoro è ancora ad apparire in una revisione JLS.

Altri suggerimenti

Ho avuto esattamente lo stesso dubbio.

Il fatto è che ogni classe che viene istanziato all'interno di altre classe ha un riferimento alla classe che racchiude nel $this variabile.

Questo è ciò che Java chiama un sintesi , non è qualcosa che si definisce per essere lì, ma qualcosa di Java fa per voi automaticamente.

Se volete vedere questo per te mettere un punto di interruzione nella linea doSomething(e) e verificare quali proprietà EventListener ha.

La mia ipotesi è che il metodo doSomething è dichiarato in classe ThisEscape, nel qual caso di riferimento certamente puo 'sfuggire'.
Vale a dire, un evento può innescare questo EventListener subito dopo la sua creazione e prima dell'esecuzione del costruttore ThisEscape è completata. E chi ascolta, a sua volta, chiamare il metodo di ThisEscape esempio.

ti modificare il vostro esempio un po '. Ora var variabile può essere letta in modo doSomething prima di essere assegnato nel costruttore.

public class ThisEscape {
    private final int var;

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

        // more initialization
        // ...

        var = 10;
    }

    // result can be 0 or 10
    int doSomething(Event e) {
        return var;
    }
}

Ho appena avuto la stessa identica domanda durante la lettura " Java Concurrency in Practice " di Brian Goetz.

Stephen C 's risposta (quella accettata) è eccellente! Ho solo voluto aggiungere in cima a quello ancora una risorsa ho scoperto. E 'da JavaSpecialists , dove il dottor Heinz M. Kabutz analizza esattamente l'esempio di codice che devnull ha scritto. Egli spiega quali classi vengono generate (esterno, interno) dopo la compilazione e come fughe this. Ho trovato questa spiegazione utile quindi mi sentivo come la condivisione :)

issue192 (dove si estende l'esempio e fornisce una condizione di competizione.)

issue192b (in cui spiega che tipo di classi sono generati dopo la compilazione e come fughe this).

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